●BZOJ 3129 [Sdoi2013]方程
題鏈:
http://www.lydsy.com/JudgeOnline/problem.php?id=3129
題解:
容斥,擴展Lucas,中國剩余定理
先看看不管限制,只需要每個位置都是正整數時的方案數的求法。
假設有 N 個未知數,加起來的和為 M。
轉化一下問題變為:"小球分配"
有 M 個相同的小球,放在 N 個盒子里,且每個盒子至少有一個的方案數。
那么方案數為 ${C}_{M-1}^{N-1}$
怎么理解這個式子呢?"插隔板法"。
使 M個小球放在一排,考慮可以在相鄰小球的空隙間(共 M-1個空隙)插入一個隔板,總共插入 N-1個隔板。
把相鄰的兩個隔板(把首尾也看作另外2個隔板)中間的區域看做一個個的盒子,區域內的小球即為該盒子里的小球。
則任意一種插隔板的方法都對應一種把小球放入盒子的方法。
所以方案數為 ${C}_{M-1}^{N-1}$
對于題目給的兩種限制,不要被嚇到了。
其實大于等于(>=W)這一個限制很好處理,直接把 M-=(W-1),即事先給這些位置分配 W-1個小球。
因為接下來按照上面的 "小球分配" 方式,這些限制一定可以滿足。
然后對于小于等于(<=W) 就需要用到容斥了。
定義 f[S] 表示至少 S 集合里面的這些"小于等于"限制都不能滿足,那么方案數的求法就是:
先強制給這些盒子分配 Wi 個小球,
使得接下來按照上面的 "小球分配" 方式求出方案數,這些盒子一定會超過限制。
所以按照容斥的做法:
答案 ANS = 至少 0 個盒子超出限制的方案數
??????????????? - 至少 1 個盒子超出限制的方案數
??????????????? +至少 2 個盒子超出限制的方案數
??????????????? -...+...
但是求那個方案數 C(N,M) 就比較麻煩了,因為 N,M很大,且模數不保證為質數,
所以用到 擴展Lucas(+國剩) 來求。
代碼:
#include<cstdio> #include<cstring> #include<iostream> #define _ % P #define filein(x) freopen(#x".in","r",stdin); #define fileout(x) freopen(#x".out","w",stdout); using namespace std; int h[300],k[300],d[300],pi[10],ppi[10],fc[10][100500],pnt; int T,N,N1,N2,M,ANS; void init(){ANS=0;memset(h,0,sizeof(h));memset(k,0,sizeof(k));memset(d,0,sizeof(d)); } void pre_prime(int P){static bool np[100500];static int prime[100500],cnt;for(int i=2;i<=100000;i++){if(!np[i]){prime[++cnt]=i;if(P%i==0){pi[++pnt]=i; ppi[pnt]=1;while(P%i==0) ppi[pnt]*=i,P/=i;}}for(int j=1;j<=cnt&&i<=100000/prime[j];j++){np[i*prime[j]]=1;if(i%prime[j]==0) break;}}for(int i=1;i<=pnt;i++){fc[i][0]=1;for(int j=1;j<=100000;j++) fc[i][j]=(1ll*fc[i][j-1]*(j%pi[i]?j:1))%ppi[i];} } int pow(int a,int b,int P){int ret=1; a=(a)_;while(b){if(b&1) ret=(1ll*ret*a)_;a=(1ll*a*a)_; b>>=1;}return ret; } void exEuclidean(int a,int b,int &g,long long &x,long long &y){if(!b) g=a,x=1,y=0;else exEuclidean(b,a%b,g,y,x),y-=(a/b)*x; } int inv(int a,int P){int g; long long x,y;exEuclidean(a,P,g,x,y);x=((1ll*x)_+P)_;return (int)x; } int fac(int n,int I,int Pi,int P){if(!n) return 1; int ret=1,r=n%P;//for(int i=2;i<=P;i++) if(i%Pi) ret=(1ll*ret*i)_;ret=fc[I][P];ret=pow(ret,n/P,P);//for(int i=2;i<=r;i++) if(i%Pi) ret=(1ll*ret*i)_;ret=(1ll*ret*fc[I][r])_;return (1ll*ret*fac(n/Pi,I,Pi,P))_; } int C(int n,int m,int I,int Pi,int P){int x,y,z,c=0;x=fac(n,I,Pi,P); y=fac(m,I,Pi,P); z=fac(n-m,I,Pi,P);for(int i=n;i;i/=Pi) c+=i/Pi;for(int i=m;i;i/=Pi) c-=i/Pi;for(int i=n-m;i;i/=Pi) c-=i/Pi;return ((((1ll*x*inv(y,P))_)*inv(z,P))_*pow(Pi,c,P))_; } int exLucas(int n,int m,int P){int ret=0,tmp; if(n<m) return 0;for(int i=1;i<=pnt;i++){tmp=C(n,m,i,pi[i],ppi[i]);tmp=((1ll*tmp*(P/ppi[i]))_*inv(P/ppi[i],ppi[i]))_;ret=(1ll*ret+tmp)_;}return ret; } int main() {int P;scanf("%d%d",&T,&P);pre_prime(P);while(T--){init();scanf("%d%d%d%d",&N,&N1,&N2,&M);for(int i=1;i<=N1;i++) scanf("%d",&d[1<<(i-1)]);for(int i=1,x;i<=N2;i++) scanf("%d",&x),M-=(x-1);for(int s=1,p;p=s&-s,s<(1<<N1);s++)h[s]=h[s^p]+1,k[s]=k[s^p]+d[p];for(int s=0,m,tmp;s<(1<<N1);s++){m=M-k[s];tmp=exLucas(m-1,N-1,P); if(h[s]&1) tmp=(-1ll*tmp+P)_;ANS=((1ll*ANS+tmp)_+P)_;}printf("%d\n",ANS);}return 0; }轉載于:https://www.cnblogs.com/zj75211/p/8040188.html
總結
以上是生活随笔為你收集整理的●BZOJ 3129 [Sdoi2013]方程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux下kill某个应用
- 下一篇: ios 自定义键盘