方伯伯的商場之旅
阿新 • • 發佈:2021-07-31
題面
題解
如果是單個的情況,將它面前的石子往中位數的位置移肯定是最優的。
但是由於數的範圍很大,這樣做只能單獨處理,所以顯然不能這麼做。
考慮上述思路的侷限性,每個數面前的石子的中位數都要單獨處理,也就是每個集合內的石子會移到不同且需要我們去求的位置,所以我們在規定的時間內無法做。
考慮我們能求出什麼,如果我們知道了每個集合內的石子往哪裡移,並且移到相同的位置,我們就可以用數位 DP 求出總花費。
但是這和我們要求的有什麼聯絡呢。
接下來就是神仙轉化。
我們先將每個集合內的石子移到位置 \(1\), 然後再從左往右掃,求出答案的減小量,如果減小量為正,就更新答案,為了把範圍內的總花費求出來,我們統計答案要以字首和的形式統計。
數位 DP 的程式碼幫助理解:
用 \(sum\)
每次把移的位置往右移一位到 \(p\),如果對於位置小於 \(p\)的,那麼把要移的位置右移一位到 \(p\) 時,總花費變大,那麼對於減小量的貢獻就是負的當前位置的值乘上多的距離 \(1\),對於在 \(p\) 右邊及在 \(p\) 上的,同理總花費變小,對減小量的貢獻為正。
因為我們數位 DP 的時候從高位往低位掃,所以每次對減小量的貢獻是從正的變成負的,所以一旦 \(sum < 0\),那麼它就不可能再變成正的了,而負的減小量是不能更新總花費的,所以直接返回 \(0\)。
LL dfs(int now, int sum, int p, int lim) { if(sum < 0) return 0; if(!now) return max(sum, 0); if(!lim && ~f[now][sum]) return f[now][sum]; int num = lim ? a[now] : k - 1; LL res = 0; for(int i = 0; i <= num; i++) res += dfs(now - 1, sum + ((p == 1) ? i * (now - 1) : (now < p ? -i : i)), p, lim & (i == num)); if(!lim) return f[now][sum] = res; return res; }
程式碼
#include<cstdio> #include<iostream> #include<cstring> using namespace std; typedef long long LL; LL f[100][10000], L, R; int k, a[100], n; LL dfs(int now, int sum, int p, int lim) { if(sum < 0) return 0; if(!now) return max(sum, 0); if(!lim && ~f[now][sum]) return f[now][sum]; int num = lim ? a[now] : k - 1; LL res = 0; for(int i = 0; i <= num; i++) res += dfs(now - 1, sum + ((p == 1) ? i * (now - 1) : (now < p ? -i : i)), p, lim & (i == num)); if(!lim) return f[now][sum] = res; return res; } LL solve(LL x) { n = 0; while(x) { a[++n] = x % k; x /= k; } LL ans = 0; for(int i = 1; i <= n; i++) { memset(f, -1, sizeof f); ans += ((i == 1) ? 1 : -1) * dfs(n, 0, i, 1); } return ans; } int main() { scanf("%lld%lld%d", &L, &R, &k); printf("%lld\n", solve(R) - solve(L - 1)); return 0; }