容斥原理算法总结(bzoj 2986 2839)

容斥原理是一个从小学就开始学习的算法。但是很多难题现在都觉得做的十分吃力。

容斥原理大概有两种表现形式,一种是按照倍数进行容斥,这个东西直接用莫比乌斯函数就可以了。

#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
",&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
",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
",ans);
}
bzoj 2839

  bzoj 1042:硬币购物一共有4种硬币。面值分别为c1,c2,c3,c4。某人去商店买东西,去了tot次。每次带di枚ci硬币,买si的价值的东西。请问每次有多少种付款方法。

  很早以前做的一道经典题目,其思路很有借鉴意义。

 

原文地址:https://www.cnblogs.com/mhy12345/p/4390949.html