[SCOI2014]方伯伯的商場之旅
Description
方伯伯有一天去參加一個商場舉辦的遊戲。商場派了一些工作人員排成一行。每個人面前有幾堆石子。說來也巧,位置在 i 的人面前的第 j 堆的石子的數量,剛好是 i 寫成 K 進制後的第 j 位。 現在方伯伯要玩一個遊戲,商場會給方伯伯兩個整數 L,R。方伯伯要把位置在 [L, R] 中的每個人的石子都合並成一堆石子。每次操作,他可以選擇一個人面前的兩堆石子,將其中的一堆中的某些石子移動到另一堆,代價是移動的石子數量 * 移動的距離。商場承諾,方伯伯只要完成任務,就給他一些椰子,代價越小,給他的椰子越多。所以方伯伯很著急,想請你告訴他最少的代價是多少。 例如:10 進制下的位置在 12312 的人,合並石子的最少代價為: 1 * 2 + 2 * 1 + 3 * 0 + 1 * 1 + 2 * 2 = 9 即把所有的石子都合並在第三堆Input
HINT
1 < = L < = R < = 10^15, 2 < = K < = 20
Solution
說白了,這個題就是給了L~R的數,每個數的每個數位是一堆石子,把這堆石子合成一個位置,求總的最小代價。
法一:GZZ法
發現,對於一個數字P,假設欽定最終合並位置是p,
調整的時候,p向左移動一位,代價變化是p及右邊所有的數位和-p左邊所有數位和。
p向右移動一位,代價變化是p及左邊所有數位和-p右邊所有數位和。
設最優的位置的數字是x,位置是p,p左邊數位和是a,右邊是b
那麽,一定有不等式:x+a-b>=0 ; x+b-a>=0 就是說,x不論往左往右移動,代價的變化總是增大的。
即:-x<=a-b<=x
所以,如果知道最終填的a-b,和x,p,就可以判斷這個p位置填x是不是左邊a,右邊b的最優解了。
枚舉p,x;
偽代碼:(cnt是最高位,進制用m,填數用k)
for(p=1~cnt)
for(x=0~m-1)
for(i=cnt~1)
for(a-b=-200~+200)
設f[i][a-b][0/1]表示,填完第i位,a-b的值,有沒有限制情況下,所有符合情況的數移動到p位置所花費的代價。
g[i][a-b][0/1]表示,f的方案數,即滿足情況的數的個數,方便轉移。
if(i==p){
continue;
}
for(k=0;k<m;k++){
if(i<p)
else
}
在i循環完之後,
for(a-b=-200~+200)
if(-x<=a-b<x) ret+=f[1][a-b][0/1]
註意這裏是<=和<,因為可能一個數字有兩個位置都是最優的合並位置,只能算一遍。
代碼:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=70; const int M=22; const int fix=201; const int up=402; ll f[N][405][2]; ll g[N][405][2]; ll L,R; int m; ll ansl,ansr; int a[N],cnt; ll wrk(){ ll ret=0; for(int p=1;p<=cnt;p++){ for(int x=0;x<m;x++){ memset(f,0,sizeof f); memset(g,0,sizeof g); g[cnt+1][fix][1]=1; for(int i=cnt;i>=1;i--){ for(int j=0;j<=up;j++){ if(i==p){ if(x<a[i]){ if(g[i+1][j][0]) g[i][j][0]+=g[i+1][j][0],f[i][j][0]+=f[i+1][j][0]; if(g[i+1][j][1]) g[i][j][0]+=g[i+1][j][1],f[i][j][0]+=f[i+1][j][1]; } else if(x==a[i]){ g[i][j][1]+=g[i+1][j][1],f[i][j][1]+=f[i+1][j][1]; g[i][j][0]+=g[i+1][j][0],f[i][j][0]+=f[i+1][j][0]; } else{ g[i][j][0]+=g[i+1][j][0],f[i][j][0]+=f[i+1][j][0]; } continue; } for(int k=0;k<m;k++){ if(i>p){//before if(j+k>up) continue; if(k<a[i]){ g[i][j+k][0]+=g[i+1][j][0],f[i][j+k][0]+=f[i+1][j][0]+(i-p)*k*g[i+1][j][0]; g[i][j+k][0]+=g[i+1][j][1],f[i][j+k][0]+=f[i+1][j][1]+(i-p)*k*g[i+1][j][1]; } else if(k==a[i]){ g[i][j+k][0]+=g[i+1][j][0],f[i][j+k][0]+=f[i+1][j][0]+(i-p)*k*g[i+1][j][0]; g[i][j+k][1]+=g[i+1][j][1],f[i][j+k][1]+=f[i+1][j][1]+(i-p)*k*g[i+1][j][1]; } else{ g[i][j+k][0]+=g[i+1][j][0],f[i][j+k][0]+=f[i+1][j][0]+(i-p)*k*g[i+1][j][0]; } } else{//after if(j-k<0) continue; if(k<a[i]){ f[i][j-k][0]+=f[i+1][j][0]+g[i+1][j][0]*(p-i)*k,g[i][j-k][0]+=g[i+1][j][0]; f[i][j-k][0]+=f[i+1][j][1]+g[i+1][j][1]*(p-i)*k,g[i][j-k][0]+=g[i+1][j][1]; } else if(k==a[i]){ f[i][j-k][0]+=f[i+1][j][0]+g[i+1][j][0]*(p-i)*k,g[i][j-k][0]+=g[i+1][j][0]; f[i][j-k][1]+=f[i+1][j][1]+g[i+1][j][1]*(p-i)*k,g[i][j-k][1]+=g[i+1][j][1]; } else{ f[i][j-k][0]+=f[i+1][j][0]+g[i+1][j][0]*(p-i)*k,g[i][j-k][0]+=g[i+1][j][0]; } } } } } for(int j=0;j<=up;j++){ if((fix-x<=j)&&(j<x+fix)){ ret+=f[1][j][0]+f[1][j][1]; } } } } return ret; } int main(){ scanf("%lld%lld",&L,&R); scanf("%d",&m); L--; cnt=0; while(L){ a[++cnt]=L%m; L/=m; } if(cnt==0){ ansl=0; } else{ ansl=wrk(); } cnt=0; while(R){ a[++cnt]=R%m; R/=m; } ansr=wrk(); printf("%lld",ansr-ansl); }
法二:大眾法。
直接欽定1號位置是最優位置,計算出來所有的總和ans
調整。
枚舉位置p從2~cnt,表示要計算從p-1移動到p,會有多少個數的代價減少多少。
代價就是,sum(1,p-1)-sum(p,cnt)
設f[i][a-b][0/1]表示,第i位,這個sum的差值,有沒有限制情況下,多少個數符合這個情況。
循環完一個p之後,
把a-b<0的f,ans-=(a-b)*f[i][a-b][0/1]
a-b>=0的不管。
這樣進行cnt次,一定可以把所有的數移動到最優解的位置。
網上題解很多,代碼就不貼了。(我也沒寫)
[SCOI2014]方伯伯的商場之旅