容斥定理问题

容斥定理学习:http://www.cppblog.com/vici/archive/2011/09/05/155103.aspx

hdu 1796 How many integers can you find

http://acm.hdu.edu.cn/showproblem.php?pid=1796

题意:给定n和一个大小为m的集合,集合元素为非负整数。求1...n - 1内能被集合里任意一个数整除的数字个数。n<=2^31,m<=10

思路:

首先明白对于集合[1,n]内能被a整除的数的个数为n/a,既能被a整除又能被b整除的数的个数为n/lcm(a,b)(a,b的最小公倍数);

容斥原理地简单应用。先找出1...n - 1内能被集合M中任意一个元素整除的个数,再减去能被集合中任意两个整除的个数,即能被它们俩的最小公倍数整除的个数,因为这部分被计算了两次,然后又加上三个时候的个数,然后又减去四个时候的倍数...直接枚举状态0...(1<<m),然后判断状态内涵几个集合元素,然后计算lcm和能被整除的个数,最后判断下集合元素的个数为奇还是偶,奇加偶减。这里回溯搜索M元素的组合也行:

这用到的是关于集合的容斥公式:

View Code
#include <iostream>

#include <cstdio>

#include <cstring>

#include <algorithm>

#include <cmath>

#include <queue>

#include <stack>

#include <set>

#include <map>

#include <string>



#define CL(a,num) memset((a),(num),sizeof(a))

#define iabs(x)  ((x) > 0 ? (x) : -(x))

#define Min(a,b) (a) > (b)? (b):(a)

#define Max(a,b) (a) > (b)? (a):(b)



#define ll long long

#define inf 0x7f7f7f7f

#define MOD 100000007

#define lc l,m,rt<<1

#define rc m + 1,r,rt<<1|1

#define pi acos(-1.0)

#define test puts("<------------------->")

#define maxn 50004

#define N 5007

#define M 200007

using namespace std;

int n,m;

int a[11];



int GCD(int x,int y)

{

    if (y == 0) return x;

    else return GCD(y,x%y);

}

int LCM(int x,int y)

{

    return (x/GCD(x,y))*y;

}

void solve(int mm)

{



    int i,j;

    int state = (1<<mm);

    int ans = 0;

    for (i = 1; i < state; ++i)//枚举所有状态m集合的组合

    {

        int bits = 0,lcm = 1;

        for (j = 0; j < mm; ++j)//查看含有那几个状态

        {

            if (i & (1<<j))

            {

                bits++;

                lcm = LCM(lcm,a[j]);//计算最小公倍数

            }

        }

        if (bits & 1) ans += (n - 1)/lcm;//求个数

        else ans -= (n - 1)/lcm;



    }

    printf("%d\n",ans);

}

int main()

{

    //freopen("din.txt","r",stdin);

    int i,x;

    while (~scanf("%d%d",&n,&m))

    {

        int mm = 0;

        for (i = 0; i < m; ++i)

        {

            scanf("%d",&x);

            if (x != 0) a[mm++] = x;//把0排除

        }

        solve(mm);

    }

    return 0;

}

 hdu 4336 Card Collector

 http://acm.hdu.edu.cn/showproblem.php?pid=4336

题意:

给出n种卡片在零食中出现的概率p[i],求如果集齐这n种卡片平均要买多少袋零食。零食里面最多只存在一张卡片。

思路:

看着像是关于概率的容斥公式其实不是,这里我们计算出存在卡片的组合要买多少张卡片才能得到,例如 0.2 0.1 买5张才能得到第一张卡片,买10张才能得到第二张卡片,如果记得到第一张又得到第二张的话要买1/(0.2 + 0.1)张卡片,这里就会存在重复性计算,所以用容斥定理排除 。其实就是一个关于集合的容斥定理公式。

View Code
#include <iostream>

#include <cstdio>

#include <cstring>

#include <algorithm>

#include <cmath>

#include <queue>

#include <stack>

#include <set>

#include <map>

#include <string>



#define CL(a,num) memset((a),(num),sizeof(a))

#define iabs(x)  ((x) > 0 ? (x) : -(x))

#define Min(a,b) (a) > (b)? (b):(a)

#define Max(a,b) (a) > (b)? (a):(b)



#define ll long long

#define inf 0x7f7f7f7f

#define MOD 100000007

#define lc l,m,rt<<1

#define rc m + 1,r,rt<<1|1

#define pi acos(-1.0)

#define test puts("<------------------->")

#define maxn 50004

#define N 5007

#define M 200007

using namespace std;



double p[22];

int n;



int main()

{

    int i,j;

    while (~scanf("%d",&n))

    {

        for (i = 0; i < n; ++i) scanf("%lf",&p[i]);



        int state = (1<<n);

        double ans = 0.0;

        for (i = 1;  i < state; ++i)//枚举所有状态

        {

            double tmp = 0.0; int bits = 0;

            for (j = 0; j < n; ++j)

            {

                if (i & (1<<j))

                {

                    bits++;

                    tmp += p[j];

                }

            }

            if (bits & 1) ans += 1.0/tmp;//1/tmp表示的是要得i状态就要买这些张卡片

            else ans -= 1.0/tmp;

        }

        printf("%.6lf\n",ans);

    }

    return 0;

}

 

 hdu 1695 GCD

http://acm.hdu.edu.cn/showproblem.php?pid=1695

题意:

给定x属于[a,b],y属于[c,d]求GCD(x,y) = k的对数,这里(x=5, y=7) and (x=7, y=5)  视为一对,a = c = 1.

0 < a <= b <= 100,000, 0 < c <= d <= 100,000, 0 <= k <= 100,000,

思路:

暴力做的话是O(n^2*log)级别的肯定超时,这里首先要明白上边给的材料里面讲的一点就是求a与[1,n]里面互素的个数可以转化成求a与[1,n]不互素的个数然m后用n - m即可。那么a与[1,n]不互素的个数怎么求呢?首先求出a的所包含的有质因子,将设他的质因子为A,B,C那么AUBUC表示能与a不互质的个数,首先求出分别与A,B,C能够整除的个数,然后减去同时能够整除A*B的个数,一次类推....就转化成了容斥定理集合的应用了。

这里我们求的GCD(x,y) = k 我们可以转化成 GCD(x/k,y/k) = 1;将设区间b < b则[1,b]与[1,b]形成的满足条件的对数就是phi[1] +phi[2] + .....+ phi[b]了。对于[1,b]与[b + 1,d]我们就采用容斥定理求求不互素的个数,然后总数减去它后得到互素个数,然后总数加上即可。

状态压缩实现:

View Code
#include <iostream>

#include <cstdio>

#include <cstring>

#include <algorithm>

#include <cmath>

#include <queue>

#include <stack>

#include <set>

#include <map>

#include <string>



#define CL(a,num) memset((a),(num),sizeof(a))

#define iabs(x)  ((x) > 0 ? (x) : -(x))

#define Min(a,b) (a) > (b)? (b):(a)

#define Max(a,b) (a) > (b)? (a):(b)



#define ll long long

#define inf 0x7f7f7f7f

#define MOD 100000007

#define lc l,m,rt<<1

#define rc m + 1,r,rt<<1|1

#define pi acos(-1.0)

#define test puts("<------------------->")

#define maxn 100004

#define N 5007

#define M 200007

using namespace std;

ll euler[maxn];

int prime[maxn][20],num[maxn];

int n;

int GCD(int x,int y)

{

    if (y == 0) return x;

    else return GCD(y,x%y);

}

int LCM(int x,int y)

{

    return x/GCD(x,y)*y;

}

void Euler()//欧拉筛选

{

    int i,j;

    CL(num,0);

    for (i = 1; i < maxn; ++i) euler[i] = i;

    for (i = 2; i < maxn; ++i)

    {

        if (euler[i] == i)

        {

            for (j = i; j < maxn; j += i)

            {

                euler[j] = euler[j]/i*(i - 1);

                prime[j][num[j]++] = i;//数j有num[j]个素数他们是i

            }

        }

        euler[i] += euler[i - 1];//累加

        //printf(">>%d\n",i);

    }

}

int main()

{

     //freopen("din.txt","r",stdin);

    int i,j,t,cas = 1;

    int a,b,c,d,k;

    Euler();

    scanf("%d",&t);

    while (t--)

    {

        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);

        if (k == 0)

        {

            printf("Case %d: 0\n",cas++);

            continue;

        }

        b /= k; d /= k;

        ll ans = 0;

        if (b > d) swap(b,d);

        ans += euler[b];

        //test;

        //printf("%d %d %d\n",b,d,ans);

        for (i = b + 1; i <= d; ++i)//枚举[b +1,d]区间

        {

            int tmp = 0;

            for (j = 1; j < (1<<num[i]); ++j)//枚举i包含素数的所有状态

            {

                //printf(">>%d %d\n",j,num[i]);

                int bits = 0,lcm = 1;

                for (k = 0; k < num[i]; ++k)

                {

                    if (j & (1<<k))

                    {

                        bits++;

                        lcm = LCM(lcm,prime[i][k]);

                    }

                }

                if (bits & 1) tmp += b/lcm;

                else tmp -= b/lcm;

            }

            ans += (b - tmp);



        }

        printf("Case %d: %I64d\n",cas++,ans);

    }

    return 0;

}

DFS实现:

View Code
#include <iostream>

#include <cstdio>

#include <cstring>

#include <algorithm>

#include <cmath>

#include <queue>

#include <stack>

#include <set>

#include <map>

#include <string>



#define CL(a,num) memset((a),(num),sizeof(a))

#define iabs(x)  ((x) > 0 ? (x) : -(x))

#define Min(a,b) (a) > (b)? (b):(a)

#define Max(a,b) (a) > (b)? (a):(b)



#define ll long long

#define inf 0x7f7f7f7f

#define MOD 100000007

#define lc l,m,rt<<1

#define rc m + 1,r,rt<<1|1

#define pi acos(-1.0)

#define test puts("<------------------->")

#define maxn 100004

#define N 5007

#define M 200007

using namespace std;

ll euler[maxn];

int prime[maxn][20],num[maxn];

int n;

int GCD(int x,int y)

{

    if (y == 0) return x;

    else return GCD(y,x%y);

}

int LCM(int x,int y)

{

    return x/GCD(x,y)*y;

}

void Euler()

{

    int i,j;

    CL(num,0);

    for (i = 1; i < maxn; ++i) euler[i] = i;

    for (i = 2; i < maxn; ++i)

    {

        if (euler[i] == i)

        {

            for (j = i; j < maxn; j += i)

            {

                euler[j] = euler[j]/i*(i - 1);

                prime[j][num[j]++] = i;

            }

        }

        euler[i] += euler[i - 1];

        //printf(">>%d\n",i);

    }

}

ll tmp;

void dfs(int x,int count,int pos,int b,int lcm)

{

    lcm = LCM(lcm,prime[pos][x]);

    if (count & 1) tmp += b/lcm;

    else tmp -= b/lcm;

    for (int i = x + 1; i < num[pos]; ++i)

    {

        dfs(i,count + 1,pos,b,lcm);

    }

}

int main()

{

    //freopen("din.txt","r",stdin);

    int i,j,t,cas = 1;

    int a,b,c,d,k;

    Euler();

    scanf("%d",&t);

    while (t--)

    {

        scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);

        if (k == 0)

        {

            printf("Case %d: 0\n",cas++);

            continue;

        }

        b /= k; d /= k;

        ll ans = 0;

        if (b > d) swap(b,d);

        ans += euler[b];



        for (i = b + 1; i <= d; ++i)

        {

            tmp = 0;

           for (j = 0; j < num[i]; ++j)//对素数的组合进行DFS求解

           dfs(j,1,i,b,1);

           ans += (b - tmp);

        }

        printf("Case %d: %I64d\n",cas++,ans);

    }

    return 0;

}

 

 

 

你可能感兴趣的:(问题)