Loading

Luogu 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\)即把所有的石子都合并在第三堆。

对于 \(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;
}
posted @ 2022-11-19 19:54  Absolutey  阅读(66)  评论(0)    收藏  举报