容斥原理专辑(1)

1、hdu 1796  How many integers can you find

容斥典型问题:求在给定区间内,能被给定集合至少一个数整除的数个数

方法:将给出的n个整除的数进行二进制枚举(2^n),计算ai所能组成的各种集合(这里将集合中ai的最小公倍数作为除数)在区间中满足的数的个数,然后利用容斥原理实现加减

注意:1、在给定的数中由于他们的关系不是互质的,所以要计算他们的lcm,而不是他们直接相乘 比如 2 4

        2、在题目中说给定的数是非负的,所以在样例会会出现0,在计算时要将其拿掉~

#include <iostream>
#define ll long long
using namespace std;

ll gcd(ll a,ll b)
{
    if(b==0) return a;
    else return gcd(b,a%b);
}

ll lcm(ll a,ll b)
{
    return a*b/gcd(a,b);
}

int main()
{
    int n,m,k;
    ll data[15],a[15];
    while(cin>>n>>m)
    {
        for(int i=0;i<m;i++)
          cin>>data[i];
        k=0;
        for(int i=0;i<m;i++)
        {
            if(data[i])
            a[k++]=data[i];
        }
        ll ans=0;
        for(int i=1;i<(1<<k);i++)
        {
            int cnt=0;
            ll mul=1;
            for(int j=0;j<k;j++)
            {
                if(i&(1<<j))
                {
                    cnt++;
                    mul=lcm(mul,a[j]);
                }
            }
            if(cnt&1) ans+=(n-1)/mul;
            else ans-=(n-1)/mul;
        }
        cout<<ans<<endl;
    }
    return 0;
}

 

2、hdu 4135 Co-prime

容斥典型问题:求指定区间内与n互素的数的个数

方法:首先利用逆向思维,求出与n不互质的个数

        考虑n的所有素因子pi(i=1…k)

         在[l,r]中有多少数能被pi整除呢?它就是:

          r/pi-(l-1)/pi

         由于有些数可能被统计多次(被好几个素因子整除)。所以,我们要运用容斥原理来解决。

         我们可以用2^k的算法求出所有的pi组合,然后计算每种组合的pi乘积,通过容斥原理来对结果进行加减处理。

         结果返回 (l-r+1)-个数

#include <bits/stdc++.h>
#define ll long long

using namespace std;

ll solve(ll a,ll b,ll n)
{
    vector <ll> p;
    for(int i=2;i*i<=n;i++)
    {
        if(n%i==0)
        {
            p.push_back(i);
            while(n%i==0)
              n=n/i;
        }
    }
    if(n>1) p.push_back(n);
    ll sum=0;
    for(int i=1;i<(1<<p.size());i++)
    {
        ll cnt=0;
        ll mult=1;
        for(int j=0;j<p.size();j++)
        {
            if(i&(1<<j))
            {
                cnt++;
                mult*=p[j];
            }
        }
        if(cnt&1) sum+=b/mult-(a-1)/mult;
        else sum-=b/mult-(a-1)/mult;
    }
    return (b-a+1)-sum;
}

int main()
{
    int t,i=0;
    ll a,b,n;
    cin>>t;
    while(t--)
    {
        cin>>a>>b>>n;
        i++;
        cout<<"Case #"<<i<<": ";
        cout<<solve(a,b,n)<<endl;
    }
    return 0;
}

 3、hdu 1695 GCD

容斥典型问题:求指定区间内与n互素的数的个数

欧拉函数问题:求指定区间内gcd(a,b)=x的对数

方法:将问题可以转化为时,的有序对的个数。

        那么先比较的 大小

        相同的部分可以用欧拉函数的累加计算,在这里采用的筛法打表形式,进行预处理

        没有公共的部分用容斥计算,求公共的区间内与非公共区间的每一个数互素的数的个数

        例如: 1 3 1 5 1

        公共部分 【1,3】 欧拉求出1~3上的累加

        非公共部分【4,5】 求出 【1,3】中与 4 互素的个数,求出 【1,3】中与 5 互素的个数,最后累加

注意:该题有 k=0 的情况,当k=0 时 ,结果直接为0 ,无需计算

#include <iostream>
#include <vector>
#define ll long long

using namespace std;
const ll maxn=100001;

ll eular[maxn];

void init()
{
    for(int i=0; i<maxn; i++)
        eular[i]=i;
    for(int i=2; i<maxn; i++)
    {
        if(eular[i]==i)
        {
            for(int j=i; j<maxn; j+=i)
                eular[j]=eular[j]/i*(i-1);
        }
    }
}

ll get_ans_1(int n)
{
    ll sum=0;
    for(int i=1; i<=n; i++)
        sum+=eular[i];
    //cout<<sum<<endl;
    return sum;
}

ll get_ans_2(int smal,int big)
{
    ll ans=0;
    //cout<<smal<<" "<<big<<endl;
    for(int i=smal+1; i<=big; i++)
    {
        vector <ll> p;
        int n=i;
        ll sum=0;
        for(int j=2; j*j<=n; j++)
        {
            if(n%j==0)
            {
                p.push_back(j);
                while(n%j==0)
                    n=n/j;
            }
        }
        if(n>1) p.push_back(n);
        //   for(int j=0;j<p.size();j++)
        //cout<<p[j]<<endl;
        for(int j=1; j<(1<<p.size()); j++)
        {
            int cnt=0;
            ll mul=1;
            for(int k=0; k<p.size(); k++)
            {
                if(j&(1<<k))
                {
                    cnt++;
                    mul=mul*p[k];
                }
            }
            if(cnt&1) sum+=smal/mul;
            else sum-=smal/mul;
        }
        ans+=smal-sum;
    }
    return ans;
}

int main()
{
    int t,i=0;
    int a,b,c,d,k;
    ll ans;
    init();
    cin>>t;
    while(t--)
    {
        cin>>a>>b>>c>>d>>k;
        i++;
        ans=0;
        if(k==0)
        {
            cout<<"Case "<<i<<": ";
            cout<<"0"<<endl;
            continue;
        }
        b=b/k;
        d=d/k;
        if(b<d)
            ans=get_ans_1(b)+get_ans_2(b,d);
        else if(b>d)
            ans=get_ans_1(d)+get_ans_2(d,b);
        else
        {
            ans=get_ans_1(b);
        }
        cout<<"Case "<<i<<": ";
        cout<<ans<<endl;
    }
    return 0;
}

 

4、hdu 2841 Visible Trees

容斥典型问题:求指定区间内与n互素的数的个数

方法:该题的重点是将应用问题转化成数学问题,我们可以发现 gcd(a,b)=1 时可以看见它

        这样就从1~n枚举找1~m与其互质的数

#include <iostream>
#include <vector>
#define ll long long

using namespace std;
const ll maxn=100001;


int main()
{
    int t;
    ll ans;
    cin>>t;
    while(t--)
    {
        int n,m;
        cin>>n>>m;
        ans=0;
        for(int i=2;i<=n;i++)
        {
            vector <ll> p;
            ll sum=0;
            int t=i;
            for(int j=2;j*j<=t;j++)
            {
                if(t%j==0)
                {
                    p.push_back(j);
                    while(t%j==0)
                      t=t/j;
                }
            }
            if(t>1) p.push_back(t);
            for(int j=1;j<(1<<p.size());j++)
            {
                int cnt=0;
                ll mul=1;
                for(int k=0;k<p.size();k++)
                {
                    if(j&(1<<k))
                    {
                        cnt++;
                        mul=mul*p[k];
                    }
                }
                if(cnt&1) sum+=m/mul;
                else sum-=m/mul;
            }
            ans+=m-sum;
        }
        cout<<ans+m<<endl;
    }
    return 0;
}

 

5、hdu 3501 Calculation 2

容斥典型问题:求指定区间内与n互素的数的个数

方法:该问题是求小于n且与n不互质数的和为多少

        我们可以先求出n的素因子, p1 p2 p3 …… pn

        那么对于 p1 在小于 n 的数的范围内有

        1*p1  2*p1  3*p1  …… x*p1 与n不互质 

        再根据等差数列的性质计算出它们的和,再利用容斥原理计算

#include <iostream>
#include <vector>
#define ll long long

using namespace std;

const ll mod=1000000007;


int main()
{
    ll n;
    while(cin>>n&&n)
    {
        if(n==1)
        {
            cout<<"0"<<endl;
            continue;
        }
        int a=n;
        vector <ll> p;
        for(int i=2;i*i<=a;i++)
        {
            if(a%i==0)
            {
                p.push_back(i);
                while(a%i==0)
                     a=a/i;
            }
        }
        if(a>1) p.push_back(a);
        ll ans=0;
        for(int i=1;i<(1<<p.size());i++)
        {
            int cnt=0;
            int mul=1;
            for(int j=0;j<p.size();j++)
            {
                if(i&(1<<j))
                {
                    cnt++;
                    mul=mul*p[j];
                }
            }
            ll num=(n-1)/mul;
            ll tmp=(mul+mul*num)*num/2;
            if(cnt&1) ans+=tmp;
            else ans-=tmp;
        }
        cout<<ans%mod<<endl;
    }
    return 0;
}

 

6、 UVA  11806  Cheerleaders

方法:利用容斥原理可以转到求“第一行、第一列、最后一行、最后一列没有石子”的方案数。

         枚举各个集合的组合时可以借助二进制进行枚举

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;

const int mod=1000007;
int data[510][510];

void get_data()
{
    memset(data,0,sizeof(data));
    for(int i=0;i<=500;i++)
    data[i][0]=1;
    for(int i=1;i<=500;i++)
     for(int j=1;j<=i;j++)
       data[i][j]=(data[i-1][j]+data[i-1][j-1])%mod;
}

int main()
{
    int t,p=0;
    cin>>t;
    get_data();
    while(t--)
    {
        p++;
        int n,m,k;
        int ans=0;
        cin>>n>>m>>k;
        for(int i=0;i<16;i++)
        {
            int cnt=0;
            int row=n,col=m;
            if(i&1) {row--;cnt++;}
            if(i&2) {row--;cnt++;}
            if(i&4) {col--;cnt++;}
            if(i&8) {col--;cnt++;}
            if(cnt%2==0) ans=(ans+data[row*col][k])%mod;
            else ans=(ans+mod-data[row*col][k])%mod;
        }
        cout<<"Case "<<p<<": ";
        cout<<ans<<endl;
    }
    return 0;
}

 

posted @ 2016-11-08 13:10  邻家那小孩儿  阅读(267)  评论(0编辑  收藏  举报