洛谷 P3286 [SCOI2014]方伯伯的商场之旅

时间:2022-03-06 20:42:30

题面

题意

给出l,r,k,求将l与r之间的数进行x操作的最小代价.
x操作指将一个数转化为k进制,表示有几堆石块,每对石块恰有该数位上的数个石子,相邻两堆距离为1,将它们并成一堆,代价为石头数量*距离.

做法

因为l和r的范围都高达1e15,故考虑数位dp,而此题难点就在于难于记录状态.
一开始想到的是枚举集中在哪一位集中和代价之和,再分别相加,而因为一个数可以在代价不变的情况下在两个不同的点上集中,难以去重.
正确做法是先把数都集中于最右端,计算出此时代价之和,然后考虑将原来集中于第二位以及其右边的,但被误判为在最右端的,减去代价并将应该不在最右端的集中于第二位,之后以此类推,将不属于前两位的集中于第三位……

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;

ll dp[60][2010],num[60],k,ans,a,b,len,mid;

inline void zh(ll u)
{
len=0;
for(; u;)
{
num[++len]=u%k;
u/=k;
}
}

ll dfs(ll now,ll sum,bool lim)
{
if(!now) return sum;
if(!lim&&dp[now][sum]!=-1) return dp[now][sum];
ll res=0,mx=k-1,i;
if(lim) mx=num[now];
for(i=0;i<=mx;i++)
{
res+=dfs(now-1,sum+(now-1)*i,lim&&i==num[now]);
}
if(!lim) dp[now][sum]=res;
return res;
}

ll Dfs(ll now,ll sum,bool lim)
{
if(sum<0) return 0;
if(!now) return sum;
if(!lim&&dp[now][sum]!=-1) return dp[now][sum];
ll res=0,i,mx=k-1;
if(lim) mx=num[now];
for(i=0;i<=mx;i++)
{
if(now<mid) res+=Dfs(now-1,sum-i,lim&&num[now]==i);
else res+=Dfs(now-1,sum+i,lim&&num[now]==i);
}
if(!lim) dp[now][sum]=res;
return res;
}

ll work()
{
ll i,res=0;
memset(dp,-1,sizeof(dp));
res=dfs(len,0,1);
for(mid=2;mid<=len;mid++)
{
memset(dp,-1,sizeof(dp));
res-=Dfs(len,0,1);
}
return res;
}

int main()
{
ll i,j;
cin>>a>>b>>k;
zh(b);
ans+=work();
zh(a-1);
ans-=work();
cout<<ans;
}