為了不誤人子弟,以后我在開頭寫出目前我做了哪些題,以防一些搜到這篇博客的人沒找到想要的內容而產生負面情緒。
目前進度:A, B, C
(你問我為什么不放在標題里面?因為看着難看啊)
開場看 A,不會。看 B,不會。
不打算了吧,反正之前打了 A 然后就自閉,這次即使把 A 做出來了也掛慘了……
沒事,rating 乃身外之物,還是把 A 做出來算了,不然太恥辱了。
30min 后……自閉。
60min 后……自閉。
75min 后……這好像是個很蠢的 dp 啊……
90min 后……溢出好煩啊……誒我怎么忘了有個東西叫 __int128(
100min 后……點名被卡?啊我個傻子為什么活生生把三個 log 寫成了六個 log……
110min 后……這 B 還是不會啊,看 C 算了。
115min 后……這真不是 sb 題?
然后就這樣,對我來說 C<A<B,卡到恰好 rk100,終於上黃了 /kel
終於能 AGC 上分了……
所以說,以后抱着 rating 身外之物的觀念,就能升分了
A
有個數,一開始是 \(0\),要把它變成 \(n\)。
將它乘 \(2\) 要 \(a\) 的代價。
將它乘 \(3\) 要 \(b\) 的代價。
將它乘 \(5\) 要 \(c\) 的代價。
將它加或減 \(1\) 要 \(d\) 的代價。
\(T\) 組數據,每次給定 \(n,a,b,c,d\),求最小代價。
\(1\le T\le 10,1\le n\le 10^{18},1\le a,b,c,d\le 10^9\)。
這真的是 AGC 的 A?我驚了……
可能有更好寫的做法,但我就是寫成了 dp。
先考慮一個暴力。暴搜出依次用了哪些乘法,然后在里面插入加減號。加減號的最小代價是個 dp,下面還要用到,這里不再贅述。
雖然這個搜看起來應該跑挺快,但它不孚眾望(注意是“孚”,不是“負”)。
注意到乘法的具體順序不是很重要,我們只關心每個加減號后面所有乘法的乘積。
如果從前到后的乘積滿足三維的偏序,那么一定可以還原出乘法序列。代價也很好算,就是所有的乘積的三個位弄一弄。
我們從后往前做(也就是加減號貢獻的乘積是遞減的)。我們把沒有被乘號隔開的加減看成一組。
設 \(d_{i,j,k,0/1}\) 表示目前考慮到 \(2^i3^j5^k\)。最后一維是 \(0\) 表示 \(n\) 與最大的小於等於 \(n\) 的 \(2^i3^j5^k\) 的倍數的差,是 \(1\) 則表示最小的大於等於 \(n\) 的。
設 \(f_{i,j,k,0/1}\) 表示目前考慮到 \(2^i3^j5^k\),表示取到上文提到的那個倍數的最小代價。
為什么要記錄 \(0/1\) 呢?因為我們可能在前面加着加着加過頭了,然后在后面減回去,可能是更優的。同時注意到我們應該枚舉到 \(2^i3^j5^k\le 2n\)。
轉移有兩種。第一種,這個是第一組加減號。那么直接算 \(0\) 到 \(d_{i,j,k,0/1}\) 的代價。
第二種,不是第一組。你可以枚舉上一組是 \((x,y,z)\),然后計算 \(d_{x,y,z,0/1}\) 到 \(d_{i,j,k,0/1}\) 的代價。這樣是六個 log,雖然跑不滿但是仍然會 T。
注意到枚舉上一組選什么沒有用,我們直接從 \(f_{i+1,j,k,0/1},f_{i,j+1,k,0/1},f_{i,j,k+1,0/1}\) 轉移就夠了。
時間復雜度 \(O(T\log^3n)\),實際上比這個還要快好多好多。
#include<bits/stdc++.h>
using namespace std;
typedef __int128 ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
const int maxn=100010;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int t;
ll n,dp[66][44][33][2],dif[66][44][33][2],ans,a,b,c,d;
bool ok[66][44][33],vld[66][44][33][2];
int main(){
t=read();
while(t--){
n=read();a=read();b=read();c=read();d=read();
ans=9e18;
int cnt=0;
ROF(i,60,0) ROF(j,40,0) ROF(k,30,0){
ull prr=1;
FOR(l,1,i){
prr*=2;
if(prr>2*n) break;
}
FOR(l,1,j){
prr*=3;
if(prr>2*n) break;
}
FOR(l,1,k){
prr*=5;
if(prr>2*n) break;
}
ok[i][j][k]=false;
if(prr>2*n) continue;
cnt++;
ll pr=prr;
ok[i][j][k]=true;
dif[i][j][k][0]=n-n/pr*pr;
dif[i][j][k][1]=(n/pr+1)*pr-n;
dp[i][j][k][0]=n/pr*d+i*a+j*b+k*c;
dp[i][j][k][1]=(n/pr+1)*d+i*a+j*b+k*c;
if(n%pr==0) dif[i][j][k][1]-=pr,dp[i][j][k][1]-=d;
FOR(x,i,min(60,i+1)) FOR(y,j,min(40,j+1)) FOR(z,k,min(k+1,30)) if(ok[x][y][z]){
if(x==i && y==j && z==k) continue;
dp[i][j][k][0]=min(dp[i][j][k][0],dp[x][y][z][0]+(dif[x][y][z][0]-dif[i][j][k][0])/pr*d);
dp[i][j][k][0]=min(dp[i][j][k][0],dp[x][y][z][1]+(dif[x][y][z][1]+dif[i][j][k][0])/pr*d);
dp[i][j][k][1]=min(dp[i][j][k][1],dp[x][y][z][0]+(dif[x][y][z][0]+dif[i][j][k][1])/pr*d);
dp[i][j][k][1]=min(dp[i][j][k][1],dp[x][y][z][1]+(dif[x][y][z][1]-dif[i][j][k][1])/pr*d);
}
else break;
if(!dif[i][j][k][0]) ans=min(ans,dp[i][j][k][0]);
if(!dif[i][j][k][1]) ans=min(ans,dp[i][j][k][1]);
assert(dp[i][j][k][0]>=0);
assert(dp[i][j][k][1]>=0);
}
long long tmp=ans;
printf("%lld\n",tmp);
}
}
B
\(n\times n\) 的矩陣。初始全是 \(1\)。
接下來 \(n\times n\) 個操作。每次給定一個還是 \(1\) 的位置,把它變成 \(0\)。
然后選擇一條從它開始,到邊界(任意一邊均可)的一條路徑。代價是這條路徑上 \(1\) 的個數。
問全過程的代價和的最小值。
\(2\le n\le 500\)。
考慮個 \(O(n^4)\) 暴力。每次 01bfs,相信大家都會。
注意到每個點的最短路是單調不升的,所以每次 01bfs 的時候可以不清空,在上一次的基礎上松弛即可。
寫一發,交上去,過了。(我當時居然沒這么干我在想什么???)
其實這個算法真實復雜度是 \(O(n^3)\)。因為一個點的最短路的最大值是 \(O(n)\) 的,而每次進入隊列肯定是因為被松弛了。所以整個過程中一個點只會進入隊列 \(O(n)\) 次。
代碼不想寫了。
C
有一個 \(0\) 到 \(3^n-1\) 的排列,初始是 \(0\) 到 \(3^n-1\) 遞增。
接下來給定一個操作序列 \(T\)。有兩種操作。
第一種:在每個數的三進制表示中,把 \(1\) 變成 \(2\),把 \(2\) 變成 \(1\)。
第二種:每個數加 \(1\) 后模 \(3^n\)。
問最后的排列。
\(1\le n\le 12,1\le |T|\le 2\times 10^5\)。
正好在 ZROI 做過類似的題,於是就做出來了,還是比較幸運的)
boboniu nb! boboniu nb!
維護一個 012 Trie,從淺到深是從低位到高位(與平時的不一樣)。
每個葉子記錄它代表的人的編號。
如果是 12 反轉,就在根上打標記,pushdown 的時候交換兩棵子樹。
如果是整體加一,那么從根開始,把子樹 1 變成子樹 0,把子樹 2 變成子樹 1,把子樹 0 變成子樹 2,然后遞歸到新的子樹 0 繼續進行這個操作。
最后 dfs 一遍還原。
時間復雜度 \(O(3^nn+|T|n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=888888;
#define MP make_pair
#define PB push_back
#define lson o<<1,l,mid
#define rson o<<1|1,mid+1,r
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define ROF(i,a,b) for(int i=(a);i>=(b);i--)
#define MEM(x,v) memset(x,v,sizeof(x))
inline ll read(){
char ch=getchar();ll x=0,f=0;
while(ch<'0' || ch>'9') f|=ch=='-',ch=getchar();
while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
return f?-x:x;
}
int n,m,q,rt,cnt,ch[maxn][3],id[maxn],ans[maxn];
bool rev[maxn];
char s[maxn];
inline void setrev(int x){
rev[x]^=1;
swap(ch[x][1],ch[x][2]);
}
inline void pushdown(int x){
if(rev[x]){
if(ch[x][0]) setrev(ch[x][0]);
if(ch[x][1]) setrev(ch[x][1]);
if(ch[x][2]) setrev(ch[x][2]);
rev[x]=false;
}
}
void build(int &x,int dep,int cur,int pr){
x=++cnt;
if(dep==n){
id[x]=cur;
return;
}
build(ch[x][0],dep+1,cur,pr*3);
build(ch[x][1],dep+1,cur+pr,pr*3);
build(ch[x][2],dep+1,cur+pr*2,pr*3);
}
void add(int x,int dep){
pushdown(x);
if(dep==n) return;
swap(ch[x][1],ch[x][2]);
swap(ch[x][0],ch[x][1]);
add(ch[x][0],dep+1);
}
void dfs(int x,int dep,int cur,int pr){
pushdown(x);
if(dep==n){
ans[id[x]]=cur;
return;
}
dfs(ch[x][0],dep+1,cur,pr*3);
dfs(ch[x][1],dep+1,cur+pr,pr*3);
dfs(ch[x][2],dep+1,cur+pr*2,pr*3);
}
int main(){
n=read();
build(rt,0,0,1);
scanf("%s",s+1);
q=strlen(s+1);
FOR(i,1,q) if(s[i]=='S') setrev(rt);
else add(rt,0);
dfs(rt,0,0,1);
m=1;
FOR(i,1,n) m*=3;
FOR(i,0,m-1) printf("%d ",ans[i]);
}