[BZOJ]1042 硬幣購物(HAOI2008)
失蹤OJ回歸。
小C通過這道題mark一下容斥一類的問題。
Description
硬幣購物一共有4種硬幣。面值分別為c1,c2,c3,c4。某人去商店買東西,去了tot次。每次帶di枚ci硬幣,買s的價值的東西。請問每次有多少種付款方法。
Input
第一行 c1,c2,c3,c4,tot 下面tot行 d1,d2,d3,d4,s。
Output
每次的方法數。
Sample Input
1 2 5 10 2
3 2 3 1 10
1000 2 2 2 900
Sample Output
4
27
HINT
di,s<=100000,tot<=1000。
Solution
O(s*tot)的DP算法誰都會寫,但是看著這時間復雜度你難道不虛嗎?
你難道甘願被卡常而就此丟掉10分或是在刷題時打一個這樣的暴力草草了事而對其中的精妙不聞不問,你的良心不會痛嗎?
如果你在解題時看到題目中有常數a(a<20)這樣的數據,不妨就往O(2a)這樣復雜度的算法去想一想。
因為題目中有一個常數4,而且還都是限制條件,所以我們就往狀壓、容斥這方面去想。
顯然狀壓是不可能的,於是我們就只有容斥了。
首先我們要知道容斥在這道題是幹嘛用的:
容斥就是對多個限制條件下方案的去重工作,也就是你們所熟知的,求多個集合的並集。
容斥常常伴隨的思想是一種逆向思維,就是題目往往要求我們去求多個集合的交,然而我們並沒有好的辦法,轉而跑去求各個集合的補的並,再補回來就是各個集合的交。
我們需要理解在這個算法中, 交 是可以O(1)求得的,然而 並 需要用容斥求得。
所以解這樣的題目的大致思路就是:
題目要求我們求A1~An的交,但是我們發現很難求;
所以我們去求CuA1~CuAn的並,而CuA1~CuAn的並需要我們求CuA1~CuAn的交,但是我們發現CuA1~CuAn的交特別好求,所以我們就圓滿地解決了這個問題。
運用這樣的思路,我們就可以很快解出這道題。
所以我們要求的只剩,在這次購物中,第 i 種硬幣用了超過di的方案數。
腦補一下,我們就知道,我們只要每種硬幣先取到它們限制的數量+1,剩下隨便取就可以了,這樣的方案無論如何都是滿足每種硬幣都超過限制的。
所以用一句話概括題解:容斥,求f[s-Σ(ci*(di+1))],f[x]為在沒有任何限制下,取硬幣得到面值x的方案數。
時間復雜度O(max(s)*4+tot*16)。註意答案最大為C(s,4)會爆int。
#include <cstdio> #include <cstring> #include <algorithm> #define ll long long #define MM 100005 using namespace std; int n,m; int a[5],g[5],ys[5]; ll f[MM],lt,ans; bool u; inline int read() { int n=0,f=1; char c=getchar(); while (c<‘0‘ || c>‘9‘) {if(c==‘-‘)f=-1; c=getchar();} while (c>=‘0‘ && c<=‘9‘) {n=n*10+c-‘0‘; c=getchar();} return n*f; } int main() { register int i,j; a[1]=read(); a[2]=read(); a[3]=read(); a[4]=read(); n=read(); f[0]=ys[1]=1; ys[2]=2; ys[3]=4; ys[4]=8; for (i=1;i<=4;++i) for (j=a[i];j<MM;++j) f[j]+=f[j-a[i]]; while (n--) { g[1]=read(); g[2]=read(); g[3]=read(); g[4]=read(); m=read(); ans=0; for (i=0;i<16;++i) { for (u=lt=0,j=1;j<=4;++j) if (i&ys[j]) u^=1,lt+=1LL*a[j]*(g[j]+1); if (lt>m) continue; ans+=(f[m-lt])*(u?-1:1); } printf("%lld\n",ans); } }
Last Word
大概就是小C關於容斥的一點點想法,當然這樣的題目還有很多無法以偏概全。希望這樣的思路能對以後有一點幫助吧。
[BZOJ]1042 硬幣購物(HAOI2008)