题意:给定 l,r,kl,r,kl,r,k ,对于一个 kkk 进制数,将数码看成这个位置的石子个数,每将一个石子移动 111 的距离需要 111 的代价。求 [l,r][l,r][l,r] 中的所有数在 kkk 进制下将石子集中在一个位置的最小代价之和。
l≤r≤1015,k≤20l\leq r\leq 10^{15},k\leq 20l≤r≤1015,k≤20
对于序列 {a1,a2,…,an}\{a_1,a_2,\dots,a_n\}{a1,a2,…,an},显然将集中位置从 k−1k-1k−1 移动到 kkk 会减少 ∑i=knai−∑i=1k−1ai\sum_{i=k}^n a_i-\sum_{i=1}^{k-1}a_i∑i=knai−∑i=1k−1ai 的代价。
枚举最优的位置,卡下前后的和的上下界,就可以数位 dp 了。
然而这个东西十分精神污染,反正我写了一天没写出来。
考虑更优美的做法。发现这个代价是关于集中位置的单峰函数,也就是一次往后移动如果贡献为负,那之后的贡献都为负。
所以有这样一个思路:先统计出放在最高位的总贡献,然后考虑每个移动产生的贡献,如果为正就把它加上。
可以设 dp(p,i,s,lim)dp(p,i,s,lim)dp(p,i,s,lim) 表示从高到低考虑到第 iii 位,如果把未考虑的记为 000,高于 ppp (含)的数码和减去低于 ppp (不含)的数码和为 sss,是否卡上界的总贡献。 p=1p=1p=1 的时候因为要算总贡献,要特殊处理一下。
为什么数位 dp 记忆化搜索这么好写啊……
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
using namespace std;
typedef long long ll;
int B;
int a[55],cnt,p;
ll f[55][20005];
ll dfs(int i,int s,int lim)
{if (!i||s<0) return max(s,0);if (!lim&&~f[i][s]) return f[i][s];int mx=lim? a[i]:B-1;ll ans=0;for (int x=0;x<=mx;x++)ans+=dfs(i-1,s+(p==1? x*(i-1):(i<p? -x:x)),lim&&x==mx);if (!lim) f[i][s]=ans;return ans;
}
inline ll calc(ll n)
{cnt=0;while (n) a[++cnt]=n%B,n/=B;memset(f,-1,sizeof(f)),p=1;ll ans=dfs(cnt,0,1);for (p=2;p<=cnt;p++) memset(f,-1,sizeof(f)),ans-=dfs(cnt,0,1);return ans;
}
int main()
{ll l,r;cin>>l>>r>>B;cout<<calc(r)-calc(l-1);return 0;
}