菜鸟系列——容斥原理

菜鸟就要老老实实重新学起:

容斥原理:

容斥原理就是求有重复覆盖区间的总和大小,

eg:

//大小为n1,n2,n3的三个集合,有多少种组合
//二集合中第i个不能与a[i]个一集合的项组合,不能与b[i]个三集合的项组合
int a[N],b[N];
long long solve(int n2,int n2,int n3)
{
    long long sum=n1*n2*n3;
    for(int i=1;i<=n2;i++)
    {
        sum-=a[i]*n3;
        sum-=b[i]*n1;
        sum+=(a[i]*b[i]);//容斥原理
    }
    return sum;
}

就是把每个区间的大小加到总和上,然后把每两个区间的相交部分减去,把每三个区间的相交部分加上,如此处理……

所以通常深搜比较好写,设置falg奇数时加上当前值,偶数时减去当前值,重点就是如何让求每一轮的值,注意不要重复,

深搜方向的选择,或者状压存状态,如此,如此……


eg:

HDU1796 How many integers can you find

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

题意:

求小于n的数中能够整除a数组中某个数的数有多少个。

思路:

可以简单求出数组中每个数在小于n的数中的答案个数,就是(n-1)/a[i],之后相加就是全部的数,但是有重复,所以要减去同时是两个数的倍数的数,

eg:a[ ] = {2,3}; sum=(n-1)/2+(n-1)/3-(n-1)/(lcm(2,3));

然而当a数组中的数多余两个时,减去的数中也会有重复,所以再减去同时是三个数的倍数的数,如此……就是容斥原理了。

code:
int n,m;
long long sum;
int a[21];

int gcd(int x,int y)
{
    return y?gcd(y,x%y):x;
}
int lcm(int x,int y)
{
    return x/gcd(x,y)*y;
}
void dfs(int now,int step,int flag)
{
    sum+=(n/step)*flag;
    for(int i=now+1;i<m;i++)
        dfs(i,lcm(max(step,a[i]),min(step,a[i])),-flag);
}
int main()
{
    int i,j,k,kk,t,x,y,z;
    while(scanf("%d%d",&n,&m)!=EOF&&n)
    {
        n--;
        for(i=0,j=0;i<m;i++)
        {
            scanf("%d",&a[i]);
            if(a[i])a[j++]=a[i];
        }
        m-=(i-j);
        sum=0;
        for(i=0;i<m;i++)
            dfs(i,a[i],1);
        printf("%lld\n",sum);
    }
    return 0;
}


POJ3695 Rectangles

http://poj.org/problem?id=3695

题意:

给出许多矩形的坐标,求每个询问中的矩形覆盖的面积大小。

思路:

看到有人用线段树扫面线过的,然而只有最多20个矩形这道题写容斥要方便得多。

离线处理,状压存下每个问题所包含的 矩形,

dfs扫到所有情况,当某个问题包含该情况时,通过flag判断包含的矩形是奇数还是偶数加减到询问答案上。

code:
#define N 112345

int n,m;
int flag,sum,ave,ans,res,len,ans1,ans2;
int a[N],b[N];
struct node
{
    int x,y;
    int xx,yy;
}tn[N];

void dfs(int x,int y,int xx,int yy,int now,int step,int flag)
{
    if(x>=xx||y>=yy)
        return ;
    if(now==n)
    {
        if(step)
            for(int i=0;i<m;i++)
                if((a[i]|step)==a[i])
                    b[i]+=flag*(xx-x)*(yy-y);
        return;
    }
    dfs(x,y,xx,yy,now+1,step,flag);
    dfs(max(x,tn[now].x),max(y,tn[now].y),min(xx,tn[now].xx),min(yy,tn[now].yy),now+1,step|(1<<now),-flag);
}
int main()
{
    int i,j,k,kk,t,x,y,z;
    kk=0;
    while(scanf("%d%d",&n,&m)!=EOF&&n)
    {
        for(i=0;i<n;i++)
            scanf("%d%d%d%d",&tn[i].x,&tn[i].y,&tn[i].xx,&tn[i].yy);
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        for(i=0;i<m;i++)
        {
            scanf("%d",&x);
            for(j=0;j<x;j++)
            {
                scanf("%d",&t);
                a[i]|=(1<<(t-1));
            }
        }
        dfs(0,0,INF,INF,0,0,-1);
        printf("Case %d:\n",++kk);
        for(i=0;i<m;i++)
            printf("Query %d: %d\n",i+1,b[i]);
        printf("\n");
    }
    return 0;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值