1. 程式人生 > 實用技巧 >[USACO06DEC]The Fewest Coins G 題解

[USACO06DEC]The Fewest Coins G 題解


首先一個顯然的思路,列舉找零的量 \(x\),那麼付錢的量就是 \(x+T\),找零是完全揹包(硬幣數無限),付錢是多重揹包(硬幣數有限),注意多重揹包要二進位制分組優化或者單調佇列優化,這裡不詳細展開了。

看到這裡有一個問題,這個 \(x\) 最大要列舉到多少?
直觀感受下,這個 \(x\) 並不會特別大。
實際上,如果面值最大的幣面值為 \(v_{max}\)\(x\leq 2\times v_{max}^2\),下面給出證明。

  • 首先,找零中面值非最大的幣總共最多\(v_{max}\) 個。

反證,如果多於 \(v_{max}\),那麼一定能找出一些硬幣,它們的面值之和 \(v_{max}\)

的倍數,可以替換成若干個面值最大的幣,使得硬幣總數減少。把這些硬幣排成一排,面值形成一個序列 \(a_i\),對序列做字首和 \(pre_i\),區間和等於字首和作差,在多於 \(v_{max}\) 個字首和中,必然存在兩個 \(pre_i\) 除以 \(v_{max}\) 餘數相同,假設它們為 \(pre_i\)\(pre_j(i<j)\),那麼 \(\sum_{i-1}^j a_i\) 這段區間和是 \(v_{max}\) 的倍數。

接下來分類討論。

  • 找零中有面值最大的幣

顯然,此時付錢中不能有面值最大的幣了,否則可以兩邊各刪一個硬幣,直到付錢中沒有或者找零中沒有為止。

所以付錢中所有的錢都不是面值最大的幣,那麼如果付錢中硬幣的數量小於 \(v_{max}\)

,付錢就小於 \(v_{max}^2\) 找零自然就小於 \(v_{max}^2\) 了。如果付錢的硬幣中多於 \(v_{max}\) 個,那麼一定存在一些為 \(v_{max}\) 的倍數,但是我們並不知道具體是幾倍,只知道這個倍數小於 \(v_{max}\)。這裡可以得出找零中最多隻有 \(v_{max}\) 個面值最大的幣

面值最大的幣最多有 \(v_{max}\) 個,面值非最大的幣最多有 \(v_{max}\) 個,所以找零總量最大為 \(2\times v_{max}^2\)

  • 找零中沒有面值最大的幣

很明顯,找零小於 \(v_{max}^2\)

所以,在 \(x>2\times v_{max}^2\)

的時候,一定可以通過一些方式把交流的硬幣總數減少,直到 \(x\leq 2\times v_{max}^2\) 為止。

程式碼:

using namespace std;
typedef long long LL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const LL V = 28800; // 2 * v_{max}^2

LL n,t;
LL v[105],c[105];
LL dp1[V << 1],dp2[V << 1];
LL ans = INF;

int main(){
	memset(dp1,INF,sizeof(dp1));
	memset(dp2,INF,sizeof(dp2));
	cin >> n >> t;
	for(LL i = 1;i <= n;i ++) cin >> v[i];
	for(LL i = 1;i <= n;i ++) cin >> c[i];
	dp1[0] = dp2[0] = 0;
	
	for(LL i = 1;i <= n;i ++)
		for(LL j = v[i];j <= V;j ++) dp2[j] = min(dp2[j],dp2[j - v[i]] + 1);
	for(LL i = 1;i <= n;i ++){
		for(LL w = 1;w <= c[i];w <<= 1){
			for(LL j = V + t;j >= v[i] * w;j --) dp1[j] = min(dp1[j],dp1[j - v[i] * w] + w);
			c[i] -= w;
		}
		if(c[i]) for(LL j = V + t;j >= v[i] * c[i];j --) dp1[j] = min(dp1[j],dp1[j - v[i] * c[i]] + c[i]);
	}
	for(LL i = t;i <= t + V;i ++) ans = min(ans,dp1[i] + dp2[i - t]);
	if(ans >= INF) ans = -1;
	cout << ans << '\n';
	return 0;
}