洛谷 P3286 [SCOI2014]方伯伯的商场之旅
题目描述
方伯伯有一天去参加一个商场举办的游戏。商场派了一些工作人员排成一行。每个人面前有几堆石子。
说来也巧,位置在 \(i\) 的人面前的第 \(j\) 堆的石子的数量,刚好是 \(i\) 写成 \(K\) 进制后的第 \(j\) 位。现在方伯伯要玩一个游戏,商场会给方伯伯两个整数 \(L,R\)。
方伯伯要把位置在 \([L, R]\) 中的每个人的石子都合并成一堆石子。每次操作,他可以选择一个人面前的两堆石子,将其中的一堆中的某些石子移动到另一堆,代价是移动的石子数量 \(×\times\) 移动的距离。
商场承诺,方伯伯只要完成任务,就给他一些椰子,代价越小,给他的椰子越多。所以方伯伯很着急,想请你告诉他最少的代价是多少。例如:\(10\) 进制下的位置在 \(12312\) 的人,合并石子的最少代价为:\(1 \times 2 + 2 \times 1 + 3 \times 0 + 1 \times1 + 2 \times 2 = 9\)即把所有的石子都合并在第三堆。
输入格式
输入仅有 \(1\) 行,包含 \(3\) 个用空格分隔的整数\(L,R,K\),表示商场给方伯伯的 \(2\) 个整数,以及进制数。
输出格式
输出仅有 \(1\) 行,包含 \(1\) 个整数,表示最少的代价。
输入输出样例
输入 #1
3 8 3
输出 #1
5
说明/提示
对于 \(100\%\) 的数据,\(1 \le L \le R \le 10^{15}, 2 \le K \le 20\)
分析
对于一堆石子来说,显然合并到石子位置的中位数是最优的
但是题中给出的石子达到了 \(10^{15}\) ,暴力枚举显然是不现实的
但是把所有的石子都合并到同一个位置的贡献是可以计算的
所以我们考虑先把所有的石子合并到编号为 \(1\) 的一堆
然后再把石子从左往右移,每次算出减小的贡献减去
如果当前的位置在修改的位置左边,那么减小的贡献减去当前的数字
否则减小的贡献加上当前的数字
因为递归时从编号较大的石子递归到编号较小的石子
所以减小的贡献一定是先增大后减小的
因此为了防止数组越界,当当前值小于 \(0\) 时,直接 \(return 0\)
代码
#include<cstdio>
#include<cstring>
#define ll long long
#define rg register
const int maxk=65,maxm=1e4+5;
ll f[maxk][maxm],l,r;
int num[maxk],cnt,k;
ll dfs(ll ws,ll tot,bool lim){
if(!ws) return tot;
if(!lim && f[ws][tot]!=-1) return f[ws][tot];
int up=lim?num[ws]:(k-1);
long long ans=0;
for(int i=0;i<=up;i++){
ans+=dfs(ws-1,tot+i*(ws-1),lim && i==up);
}
if(!lim) f[ws][tot]=ans;
return ans;
}
ll dfs2(ll ws,ll tot,int xg,bool lim){
if(tot<0) return 0;
if(!ws) return tot;
if(!lim && f[ws][tot]!=-1) return f[ws][tot];
int up=lim?num[ws]:(k-1);
long long ans=0;
for(int i=0;i<=up;i++){
ans+=dfs2(ws-1,tot+(ws<xg?-i:i),xg,lim && i==up);
}
if(!lim) f[ws][tot]=ans;
return ans;
}
ll solve(ll now){
cnt=0;
while(now){
num[++cnt]=now%k;
now/=k;
}
memset(f,-1,sizeof(f));
long long nans=dfs(cnt,0,1);
for(int i=2;i<=cnt;i++){
memset(f,-1,sizeof(f));
nans-=dfs2(cnt,0,i,1);
}
return nans;
}
int main(){
scanf("%lld%lld%d",&l,&r,&k);
printf("%lld\n",solve(r)-solve(l-1));
return 0;
}