[BZOJ3598][SCOI2014]方伯伯的商场之旅(数位DP,记忆化搜索)
3598: [Scoi2014]方伯伯的商场之旅
Time Limit: 30 Sec Memory Limit: 64 MB
Submit: 449 Solved: 254
[Submit][Status][Discuss]Description
方伯伯有一天去参加一个商场举办的游戏。商场派了一些工作人员排成一行。每个人面前有几堆石子。说来也巧,位置在 i 的人面前的第 j 堆的石子的数量,刚好是 i 写成 K 进制后的第 j 位。现在方伯伯要玩一个游戏,商场会给方伯伯两个整数 L,R。方伯伯要把位置在 [L, R] 中的每个人的石子都合并成一堆石子。每次操作,他可以选择一个人面前的两堆石子,将其中的一堆中的某些石子移动到另一堆,代价是移动的石子数量 * 移动的距离。商场承诺,方伯伯只要完成任务,就给他一些椰子,代价越小,给他的椰子越多。所以方伯伯很着急,想请你告诉他最少的代价是多少。例如:10 进制下的位置在 12312 的人,合并石子的最少代价为:1 * 2 + 2 * 1 + 3 * 0 + 1 * 1 + 2 * 2 = 9即把所有的石子都合并在第三堆Input
输入仅有 1 行,包含 3 个用空格分隔的整数 L,R,K,表示商场给方伯伯的 2 个整数,以及进制数Output
输出仅有 1 行,包含 1 个整数,表示最少的代价。
Sample Input
3 8 3Sample Output
5HINT
1 < = L < = R < = 10^15, 2 < = K < = 20
Source
一眼数位DP,但是具体实现感觉非常难以理解。
主要思路是:先默认每个人都将式子移到最低的那一位上去,然后从2开始枚举如果将其中一些人的石子从i-1移到i最终结果会优多少。
第一个DP就是普通的数位DP,但是第二个DP就有一个很难处理的问题:如何确定到底那些人的石子可以(或需要)从i-1移到i,因为每个人最终所选的那一位都不同。
这里有一种新型的记忆化搜索,dfs(i,sum,lim)表示(从高到低)前i位将(那些可以或需要移动的)人移动,且前i位已经变优了sum,所能得到的最终解(也就是最后最多能变优多少)。一般记搜都是形如dfs(i)+=dfs(i+1)+f(i)的(最终返回的sum),但这种记搜是dfs(i,sum,lim)+=dfs(i+1,sum+f[i],lim')(最终返回的仍然是sum,只是在递归底层被处理过的sum)。也就是说,普通记搜记录的是这个状态到目标状态(递归底层)的信息,而这种记录的是初始状态到这个状态的信息。
这种记搜实际上不应该从DP的角度去理解,而是应该从搜索的角度,实际上就是一个记录了中间状态(sum)的搜索,在底层进行处理。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define rep(i,l,r) for (int i=l; i<=r; i++) 5 #define ll long long 6 using namespace std; 7 8 int k,num[60]; 9 ll l,r,len,f[60][9*20*20][2]; 10 11 ll dfs1(int pos,int sum,bool lim){ 12 if (pos>len) return sum; 13 if (f[pos][sum][lim]!=-1) return f[pos][sum][lim]; 14 ll res=0; int end=(lim?num[pos]:k-1); 15 rep(i,0,end) res+=dfs1(pos+1,sum+i*(pos-1),lim && (i==end)); 16 return f[pos][sum][lim]=res; 17 } 18 19 ll dfs2(int pos,int sum,int m,bool lim){ 20 if (pos>len) return max(sum,0); 21 if (f[pos][sum][lim]!=-1) return f[pos][sum][lim]; 22 ll res=0; int end=(lim?num[pos]:k-1); 23 rep(i,0,end) res+=dfs2(pos+1,sum+((pos<m)?-i:i),m,lim && (i==end)); 24 return f[pos][sum][lim]=res; 25 } 26 27 ll solve(ll n){ 28 len=0; while (n) num[++len]=n%k,n/=k; 29 reverse(num+1,num+len+1); 30 memset(f,-1,sizeof(f)); 31 ll res=dfs1(1,0,1); 32 rep(i,2,len) memset(f,-1,sizeof(f)),res-=dfs2(1,0,i,1); 33 return res; 34 } 35 36 int main(){ 37 freopen("bzoj3598.in","r",stdin); 38 freopen("bzoj3598.out","w",stdout); 39 scanf("%lld%lld%d",&l,&r,&k); 40 printf("%lld\n",solve(r)-solve(l-1)); 41 return 0; 42 }