1. 程式人生 > 其它 >方伯伯的商場之旅

方伯伯的商場之旅

題面

方伯伯的商場之旅

題解

如果是單個的情況,將它面前的石子往中位數的位置移肯定是最優的。
但是由於數的範圍很大,這樣做只能單獨處理,所以顯然不能這麼做。
考慮上述思路的侷限性,每個數面前的石子的中位數都要單獨處理,也就是每個集合內的石子會移到不同且需要我們去求的位置,所以我們在規定的時間內無法做。
考慮我們能求出什麼,如果我們知道了每個集合內的石子往哪裡移,並且移到相同的位置,我們就可以用數位 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;
}