容斥定理问题

容斥定理学习: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;
}

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值