题意:给定 l , r , k l,r,k l,r,k ,对于一个 k k k 进制数,将数码看成这个位置的石子个数,每将一个石子移动 1 1 1 的距离需要 1 1 1 的代价。求 [ l , r ] [l,r] [l,r] 中的所有数在 k k k 进制下将石子集中在一个位置的最小代价之和。
l ≤ r ≤ 1 0 15 , k ≤ 20 l\leq r\leq 10^{15},k\leq 20 l≤r≤1015,k≤20
对于序列 { a 1 , a 2 , … , a n } \{a_1,a_2,\dots,a_n\} {a1,a2,…,an},显然将集中位置从 k − 1 k-1 k−1 移动到 k k k 会减少 ∑ i = k n a i − ∑ i = 1 k − 1 a i \sum_{i=k}^n a_i-\sum_{i=1}^{k-1}a_i ∑i=knai−∑i=1k−1ai 的代价。
枚举最优的位置,卡下前后的和的上下界,就可以数位 dp 了。
然而这个东西十分精神污染,反正我写了一天没写出来。
考虑更优美的做法。发现这个代价是关于集中位置的单峰函数,也就是一次往后移动如果贡献为负,那之后的贡献都为负。
所以有这样一个思路:先统计出放在最高位的总贡献,然后考虑每个移动产生的贡献,如果为正就把它加上。
可以设 d p ( p , i , s , l i m ) dp(p,i,s,lim) dp(p,i,s,lim) 表示从高到低考虑到第 i i i 位,如果把未考虑的记为 0 0 0,高于 p p p (含)的数码和减去低于 p p p (不含)的数码和为 s s s,是否卡上界的总贡献。 p = 1 p=1 p=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;
}