题意
方伯伯有一天去参加一个商场举办的游戏。商场派了一些工作人员排成一行。每个人面前有几堆石子。
说来也巧,位置在 \(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\)即把所有的石子都合并在第三堆。
对于 \(100\%\) 的数据,\(1 \le L \le R \le 10^{15}, 2 \le K \le 20\)。
题解
一道很不错的数位dp
首先考虑怎么计算每个人的最少代价. 这里我们要有调整递推的思想.
我们首先计算在\(1\)点的贡献,然后转移到\(2\)的时候,加上\(2-1\)的.
虽然我们也有其他\(O(\log_{k} i)\)的时间复杂度内甚至更简洁的做法.
但是这种方法更有优化空间. 算是一个套路吧.
然后我们可以考虑用数位dp, 分别求一开始\(1\), 和后面\(i - i-1\)的贡献.
如果\(i - i-1 < 0\), 以后只会更劣.
显然这个东西是一个单峰的(中位数证明), 所以解法正确.
代码
点击查看代码
#include <stdio.h>
#include <string.h>
#include <iostream>
#define LL long long
using namespace std;
LL L, R, k, a[60], n, ans, j, f[30][2][10005];
LL dfs(int x, int sta, int sum) {
if (x == 0) return max(0, sum);
if (f[x][sta][sum] != -1) return f[x][sta][sum];
f[x][sta][sum] = 0; int lim = sta ? a[x] : k - 1; LL res = 0;
for(int i = 0; i <= lim; ++i)
res += dfs(x - 1, sta && (i == lim), sum + (j == 1 ? (x - 1) * i : ((x >= j ? i : (-i)))));
if (sum >= 0) f[x][sta][sum] = res;
return res;
}
LL solve(LL val) {
for(n = 0; val; val /= k) a[++n] = val % k; // 第n位是高位
for(j = 1, ans = 0; j <= n; ++j) {
memset(f, -1, sizeof(f));LL tmp = dfs(n, 1, 0);
if (tmp < 0) continue;
ans += (j == 1 ? 1 : -1) * tmp;
}
return ans;
}
int main() {
scanf("%lld%lld%lld", &L, &R, &k);
printf("%lld\n", solve(R) - solve(L - 1));
return 0;
}