【BZOJ3598】【SCOI2014】方伯伯的商场之旅

时间:2022-01-20 10:32:23

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

网上有人说这是极水的数位DP,在考场上属于秒A的题,但我却想了很久没想出来。。。怎么办啊,自己数位DP白学了。看着题解还是勉强懂了这道题。。。

意思就是可以先把所有的数都合并到第一位,然后就可以发现有些数肯定是不满足合并到第一位是最优的,我们对于这样的数,考虑把它的合并位置从第一位移动到第二位,那么合并代价会变,但这个变多少很容易就能推出来。那么又从第二位移动到第三位。。 以此类推一直到最后一位。。。
由此得出要写两个DP。。。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long llint;

llint F[64][4000],L,R,K;
llint Num[70];

llint Dfs1(llint n,llint s,bool lim)
{
if(!n)return s;
if(!lim&&F[n][s]!=-1)return F[n][s];
llint ret=0,k;
k = lim ? Num[n] : K-1;
for(llint i=0;i<=k;i++){
ret += Dfs1(n-1,s+i*(n-1),lim&(Num[n]==i));
}
if(!lim) F[n][s] = ret;
return ret;
}

llint Dfs2(llint n,llint s,llint M,bool lim){
if(s<0)return 0;
if(!n) return s;
if(!lim&&F[n][s]!=-1) return F[n][s];
llint ret=0,k;
k = lim ? Num[n] : K-1;
for(llint i=0;i<=k;i++){
if(n>=M) ret+=Dfs2(n-1,s+i*1,M,lim&(Num[n]==i));
else ret+=Dfs2(n-1,s-i*1,M,lim&(Num[n]==i));
}
if(!lim)F[n][s] = ret;
return ret;
}

llint Getans(llint n){
llint pos=0;
memset(F,-1,sizeof(F));
while(n){
Num[++pos] = n%K;
n/=K;
}
llint ret=Dfs1(pos,0,1);
for(llint i=2;i<=pos;i++){
memset(F,-1,sizeof(F));
ret -= Dfs2(pos,0,i,1);
}
return ret;
}

int main()
{
cin>>L>>R>>K;
llint ans = Getans(R)-Getans(L-1);
cout<<ans<<endl;
return 0;
}