P3286 [SCOI2014]方伯伯的商场之旅 题解

这道题是道套路不太一般的数位 DP。

首先根据小学奥数知识我们可以得知:所有石子合并到最中间一定是最优的,然而这并没有什么用,也不知道什么在中间。

那么我们先思考一个问题:假设当前合并点为 \(tag\),当我们将合并点更新为 \(tag+1\) 时,记 \(tag+1\) 时的答案为 \(ans_{tag+1}\),第一位答案为 \(ans_1\),那么:\(ans_{tag+1}-ans_1\) 是否具有单调性?

答案是:是。

为什么?我们将合并点不断右移的时候,显然更多的点会到左边,此时左边的石头会越来越多,导致每移动一格影响就会越来越大,因此有单调性。

那么我们就有了一种思路:首先先计算出合并到 1 号点的答案,然后贪心右移,答案能变小就变小。

于是这道题就做完了

到目前为止还没有做完,因为代码写不出来。

这道题的特别之处在于我们要写两个 dfs

  1. LL dfs1(int pos, int sum, int limit)
    \(sum\) 表示已经算完位的贡献。
  2. LL dfs2(int pos, int sum, int tag, int limit)
    \(tag\) 是新的合并点。
    而在 dfs2 中,我们需要计算的是新的左边贡献减去右边贡献的差值,相当于一种前缀和的思想,如果算出来是正数,那么更新答案。

到这里就做完了。

代码注意:

  1. 随时清空 \(f\) 数组。
  2. 注意数位上界是 \(k-1\)

代码:

#include <bits/stdc++.h>
#define Max(a, b) ((a > b) ? a : b)
using namespace std;

typedef long long LL;
const int MAXN = 1e5 + 10;
LL l, r, f[70][MAXN];
int k, cnt, a[70];

LL read()
{
	LL sum = 0, fh = 1; char ch = getchar();
	for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = (sum << 3) + (sum << 1) + (ch ^ 48);
	return (fh == 1) ? sum : -sum;
}

LL dfs1(int pos, int sum, int limit)
{
	if (pos == 0) return sum;
	if (!limit && f[pos][sum] != -1) return f[pos][sum];
	int t = limit ? a[pos] : k - 1; LL ans = 0;
	for (int i = 0; i <= t; ++i) ans += dfs1(pos - 1, sum + i * (pos - 1), limit && i == a[pos]);
	if (!limit) f[pos][sum] = ans;
	return ans;
}

LL dfs2(int pos, int sum, int tag, int limit)
{
	if (sum < 0) return 0;
	if (pos == 0) return sum;
	if (!limit && f[pos][sum] != -1) return f[pos][sum];
	int t = limit ? a[pos] : k - 1; LL ans = 0;
	for (int i = 0; i <= t; ++i) ans += dfs2(pos - 1, sum + ((pos < tag) ? -i : i), tag, limit && i == a[pos]);
	if (!limit) f[pos][sum] = ans;
	return ans;
}

LL Get(LL p)
{
	memset(f, -1, sizeof(f)); cnt = 0;
	for (; p; p /= k) a[++cnt] = p % k;
	LL sum = dfs1(cnt, 0, 1);
	for (int i = 2; i <= cnt; ++i)
	{
		memset(f, -1, sizeof(f));
		sum -= dfs2(cnt, 0, i, 1);
	}
	return sum;
}

int main()
{
	l = read(), r = read(), k = read();
	printf ("%lld\n", Get(r) - Get(l - 1));
	return 0;
}
posted @ 2022-04-15 19:38  Plozia  阅读(22)  评论(0编辑  收藏  举报