P3286 [SCOI2014]方伯伯的商场之旅 题解
这道题是道套路不太一般的数位 DP。
首先根据小学奥数知识我们可以得知:所有石子合并到最中间一定是最优的,然而这并没有什么用,也不知道什么在中间。
那么我们先思考一个问题:假设当前合并点为 \(tag\),当我们将合并点更新为 \(tag+1\) 时,记 \(tag+1\) 时的答案为 \(ans_{tag+1}\),第一位答案为 \(ans_1\),那么:\(ans_{tag+1}-ans_1\) 是否具有单调性?
答案是:是。
为什么?我们将合并点不断右移的时候,显然更多的点会到左边,此时左边的石头会越来越多,导致每移动一格影响就会越来越大,因此有单调性。
那么我们就有了一种思路:首先先计算出合并到 1 号点的答案,然后贪心右移,答案能变小就变小。
于是这道题就做完了
到目前为止还没有做完,因为代码写不出来。
这道题的特别之处在于我们要写两个 dfs
。
LL dfs1(int pos, int sum, int limit)
\(sum\) 表示已经算完位的贡献。LL dfs2(int pos, int sum, int tag, int limit)
\(tag\) 是新的合并点。
而在dfs2
中,我们需要计算的是新的左边贡献减去右边贡献的差值,相当于一种前缀和的思想,如果算出来是正数,那么更新答案。
到这里就做完了。
代码注意:
- 随时清空 \(f\) 数组。
- 注意数位上界是 \(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;
}