容斥原理是一个从小学就开始学习的算法。但是很多难题现在都觉得做的十分吃力。
容斥原理大概有两种表现形式,一种是按照倍数进行容斥,这个东西直接用莫比乌斯函数就可以了。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define MAXN 200100 typedef long long qword; bool pflag[MAXN]; int prime[MAXN],topp=-1; int mu[MAXN]; int a[MAXN],tota; int b[MAXN],totb; void init() { for (register int i=2;i<MAXN;i++) { if (!pflag[i]) { prime[++topp]=i; mu[i]=1; } for (register int j=0;j<=topp && i*prime[j]<MAXN;j++) { pflag[i*prime[j]]=true; if (i%prime[j]==0) { mu[i*prime[j]]=0; break; } mu[i*prime[j]]=-mu[i]; } } } int main() { //freopen("input.txt","r",stdin); qword tot; scanf("%lld\n",&tot); init(); for (register int i=1;i<MAXN;i++) if (mu[i]==1) a[tota++]=i; else if (mu[i]==-1) b[totb++]=i; register qword l,r,mid; register qword ans=0; l=0,r=26000000000LL; while (l+1<r) { mid=(l+r)>>1; ans=0; for (register int i=0;i<tota && (qword)a[i]*a[i]<=mid;i++) ans=ans+mid/((qword)a[i]*a[i]); for (register int i=0;i<totb && (qword)b[i]*b[i]<=mid;i++) ans=ans-mid/((qword)b[i]*b[i]); if (ans>=tot) r=mid; else l=mid; } printf("%lld\n",r); }
bzoj 2986
另一种可以理解为一个一个递推,按照+/-1容斥。
bzoj 2839 : 一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为K,求取法的方案数,答案模1000000007。(是质数喔~)
这道题虽然说懂了正解,但是还是没有弄清楚最初的做法为什么错了。
考虑求大于等于i的方案数,可理解为从n个选出i个,再在另n-i个里面任意选择,由于答案的k个肯定属于这i个,所以容斥时再乘上系数C(i,k),直接容斥即可。
这种题如果方法有问题其实是很难检查出来的,最好的方法是写暴力或者用另一种思路写。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define MAXN 1000100 #define MOD 1000000007 typedef long long qword; qword fact[MAXN]; qword pow_mod(qword x,qword y) { qword ret=1; while (y) { if (y&1) ret=ret*x%MOD; x=x*x%MOD; y>>=1; } return ret; } qword finv[MAXN]; #define C(x,y) (fact[x]*finv[y]%MOD*finv[(x)-(y)]%MOD) int main() { freopen("input.txt","r",stdin); int n,m; scanf("%d%d",&n,&m); fact[0]=1; for (register int i=1;i<=n;i++) fact[i]=fact[i-1]*i%MOD; finv[n]=pow_mod(fact[n],MOD-2); for (register int i=n-1;i>=0;i--) finv[i]=finv[i+1]*(i+1)%MOD; register qword ans=0; register qword tmp=2; ans+=((n-m)%2==0?1:-1)*C(n,n)*tmp%MOD*C(n,m)%MOD; for (register int i=n-1;i>=m;i--) { tmp=tmp*tmp%MOD; ans+=((i-m)%2==0?1:-1)*C(n,i)*tmp%MOD*C(i,m)%MOD; ans%=MOD; } //ans=h[m]*C(n,m); printf("%lld\n",ans); }
bzoj 2839
bzoj 1042:硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买si的价值的东西。请问每次有多少种付款方法。
很早以前做的一道经典题目,其思路很有借鉴意义。
时间: 2024-10-31 22:21:34