[BZOJ3598][SCOI2014]方伯伯的商场之旅(数位DP,记忆化搜索)

时间:2022-12-16 09:35:55

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 3

Sample Output

5

HINT

 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 }