kmjp's blog

競技プログラミング参加記です

yukicoder : No.42 貯金箱の溜息

これは当時yukicoderを初めて初の自力で解けない問題だった。
http://yukicoder.me/problems/22

問題

1,5,10,50,100,500円の硬貨が無限にある。
これらを使いM円を支払う方法は何通りあるか。

解法

小さい順にx種類の硬貨を使いM円支払う方法を考える。
M=m+p*500 (m=M%500)とし、x種類の硬貨を使いM円支払う方法をf(x,m,p)とする。

この関数fはpに対し(x-1)次式になる。今回は硬貨が6種類なので5次式。
よって3000円程度までDPで組み合わせを求めておけば、p=0~5の時のf(x,m,p)がわかるので、あとはラグランジュ補間でf(x,M%500,M/500)を答えればよい。

ll mo=1000000009;
ll dp[100501];

ll rev(ll a) {
	ll r=1, n=mo-2;
	while(n) r=r*((n%2)?a:1)%mo,a=a*a%mo,n>>=1;
	return r;
}

ll dodo(ll V) {
	int i,x;
	ll quo=V/500, rem=V%500, ret=0;
	
	FOR(i,6) {
		ll a=1,b=1;
		FOR(x,6) if(i!=x) a=a*(mo-((quo-x)%mo))%mo;
		FOR(x,6) if(i!=x) b=b*(mo-(i-x))%mo;
		ret+=dp[rem+i*500]*a%mo*rev(b)%mo;
	}
	
	return ret%mo;
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	FOR(i,100400) dp[i]=1;
	for(i=5;i<=100000;i++) dp[i]=(dp[i]+dp[i-5])%mo;
	for(i=10;i<=100000;i++) dp[i]=(dp[i]+dp[i-10])%mo;
	for(i=50;i<=100000;i++) dp[i]=(dp[i]+dp[i-50])%mo;
	for(i=100;i<=100000;i++) dp[i]=(dp[i]+dp[i-100])%mo;
	for(i=500;i<=100000;i++) dp[i]=(dp[i]+dp[i-500])%mo;

	int T;
	ll V;
	cin>>T;
	while(T--) cin>>V, cout<< dodo(V) << endl;
}

まとめ

この時ラグランジュ補間を練習したおかげでARC033のDが解けたね。