ACM 容斥原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gg_gogoing/article/details/43635057

VJ 点击打开链接

参考 点击打开链接 

非常好的译文:点击打开链接

容斥原理的想法就是求多个集合的并集.所以要先设计好集合.

组合数学问题中,正面解决会困难,常用方法是正难则反,使用容斥原理求反向在用全集减去.将对立面的限制条件分析清楚.

eg 求区间互质的数的个数,则用除法等计算出一个数的倍数的方法再减去.

BC25第2题想到的是容斥,独立的取考虑了行的情况与列的情况(而题解中有一个假设就是当行都满足的时候,即先考虑列的情况,然后那些行如何放,我深陷在了求得行,求得列再容斥就混乱了,容斥是单一集合)。分阶段一行一行放,对于每一行有操作,状态设定区分开。dp[i][j][k] 第i行,j列被占用,用了k个石子


 UVa 11806 Cheerleaders
求k个石子放在n*m的矩阵里 并且第一行 最后一行 第一列 最后一列都要有石子
考虑反面 求出所有的 减去不满足的情况
容斥原理总共4个 集合A(第一行没有石子) B(最后行没有石子)C(第一列没有石子)D(最后一列没有石子)
减去1个集合的 加上2个集合的 减去3个集合的 加上4个集合的

#include <cstring>
#include <cstdio>

const int maxn = 510;
const int mod = 1000007;
int C[maxn][maxn];

int main()
{
	C[0][0] = 1;
	for(int i = 0; i <= 500; i++){
		C[i][0] = C[i][i] = 1;
		for(int j = 1; j < i; j++)
			C[i][j] = (C[i-1][j] + C[i-1][j-1]) %mod;
	}
	int T;
	int cas = 1;
	scanf("%d", &T);
	while(T--)
	{
		int  n, m, k, sum = 0;
		scanf("%d %d %d", &n, &m, &k);
		for(int s = 0; s < 16; s++){
			int b = 0, r = n, c = m;
			if(s&1)
				r--,b++;
			if(s&2)
				r--,b++;
			if(s&4)
<span style="white-space:pre">				</span>c--,b++;
			if(s&8)
				c--,b++;
			if(b&1)
				sum = ((sum - C[r*c][k]) % mod + mod) % mod;
			else
				sum = (sum + C[r*c][k]) % mod;
		}
		printf("Case %d: %d\n", cas++, sum);
	}
	return 0;
}

HDU 1796 How many integers can you find

练习DFS式列举

#include<stdio.h>
#include<string.h>
int a[20], N, M, la ;
int hash[20];
__int64 gcd(__int64 a, __int64 b)
{
    __int64 c ;
    while(b)
     {
         c = a % b ;
         a = b ;
         b = c ;
     }
    return a ;
}
//             当前点   已经加入容斥的个数,记录容斥的过程值   结果
void dfs(int now, int count, __int64 lcm, __int64 &sum)
{
    lcm = a[now] / gcd(lcm, a[now]) * lcm ;
    if(count & 1)
         sum += (N - 1) / lcm ;
    else sum -= (N - 1) / lcm ;
    for(int i = now + 1; i < M; i++)
         dfs(i, count + 1, lcm, sum);
}
int main()
{
    int b,i ;
    while(scanf("%d%d", &N, &M) != EOF)
     {
        for(i = 0, la = 0; i < M; i++)
         {
             scanf("%d", &b);
            if(b)
             {
                 a[la++] = b ;
             }
         }
         M = la ;
        __int64 sum = 0 ;
        //memset(hash,0,sizeof(hash));
        for(i = 0; i < M; i++) //枚举起点
             dfs(i, 1, a[i], sum);
         printf("%I64d\n", sum);
     }
    return 0 ;
}

HDU 1695 GCD

在(a,b) ,(c,d) 区间内选两个数使得GCD(X,Y)=k  那么我们直接将区间范围/k,则求互质的数字即可,枚举时注意控制X<Y 就行.

/* ***********************************************
Author        :bingone
Created Time  :2015-1-26 11:47:59
File Name     :HDU1685.cpp
************************************************ */

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#define eps 1e-8
#define inf 0x3f3f3f3f
#define maxn (10+100000)
#define zero(x) (fabs(x)<eps)
typedef long long ll;
using namespace std;
int N,M,T,CNT;
int num[maxn];
vector<int>  yz[maxn];
ll ans;
void DEBUG(){
    for(int i=0;i<maxn;i++){
        cout<<i<<endl;
        for(int j=0;j<yz[i].size();j++)
            printf("%d ",yz[i][j]);
        cout<<endl;
    }
}
void init(){
	memset(num,0,sizeof(num));
	for(int i=0;i<maxn;i++) yz[i].clear();
	for(int i=2;i<maxn;i++) if(num[i]==0){
        for(int j=i;j<maxn;j+=i){
            yz[j].push_back(i);
            num[j]=1;
        }
    }
	///DEBUG();
}
void dfs(int loc,int pos,int cnt,int num,int t){
	if(pos>=yz[loc].size()) return;
	if(cnt & 1) ans+=t-t/num;
	else ans-=t-t/num;
	for(int i=pos+1;i<yz[loc].size();i++)
		dfs(loc,i,cnt+1,num*yz[loc][i],t);
}

int main()
{
    //freopen("1.out","r",stdin);
    //freopen("out.txt","w",stdout);
	init();int cas=1;
	scanf("%d",&T);
	while(T--){
		int a,b,c,d,k;
		scanf("%d%d%d%d%d",&a,&b,&c,&d,&k);
		if(k==0){printf("Case %d: 0\n",cas++);continue;}
		if(b>d) swap(b,d);
		b/=k;d/=k;ans=0;
		if(b) ans=d;
		for(int i=2;i<=b;i++)
			for(int j=0;j<yz[i].size();j++)
				dfs(i,j,1,yz[i][j],d-i);
		printf("Case %d: %I64d\n",cas++,ans);
    }
    return 0;
}

HDU 2204 Eddy's爱好

任何一个数字被表示乘一个数的质数次幂是唯一的.M^K K位质数.还有就是两个质数的乘积这种情况减去,奇加偶减.

因为三个质数乘积2*3*7=42  2^42>10^18 所以就到三就满足了.

/* ***********************************************
Author        :bingone
Created Time  :2015-1-26 11:47:59
File Name     :HDU1685.cpp
************************************************ */

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#define eps 1e-8
#define inf 0x3f3f3f3f
#define maxn (10+1000)
#define zero(x) (fabs(x)<eps)
typedef long long ll;
using namespace std;
int N,M,T,CNT;
int num[100];
int prim[100];
void init(){
	memset(num,0,sizeof(num));
	CNT=0;
	for(int i=2;i<100;i++){
		if(num[i]==0) prim[CNT++]=i;
		for(int j=0;j<CNT;j++){
			if(i*prim[j]>100) break;
			num[i*prim[j]]=1;
			if(i%prim[j]==0) break;
		}
	}
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
    ll n;
	init();
	while(~scanf("%I64d",&n)){
		ll ans=1;
		for(int i=0;i<CNT;i++){
			ll tmp=(ll)(pow((double)n,1./prim[i])+eps);
			if(pow((double)tmp,prim[i])>n) tmp--;
			if(tmp<2) break;
			ans+=(ll)tmp-1;
		}
		for(int i=0;i<CNT;i++)
			for(int j=i+1;j<CNT;j++){
				int tt=prim[i]*prim[j];
				ll tmp=(ll)(pow((double)n,1./tt)+eps);
				if(pow((double)tmp,tt)>n) tmp--;
				if(tmp<2) break;
				ans-=(ll)tmp-1;
			}
		ll tmp=(ll)((double)pow(n,1./30)+eps);
		if(pow((double)tmp,30)>n) tmp--;
		if(tmp>=2) ans+=(ll)tmp-1;
		tmp=(ll)(pow((double)n,1./42)+eps);
		if(pow((double)tmp,42)>n) tmp--;
		if(tmp>=2) ans+=(ll)tmp-1;
		printf("%I64d\n",ans);
	}
    return 0;
}

HDU 4407

这个一样的道理,但是有个更新操作,规模1000,用map最后在计算下就OK了. 注意map 操作 ->的运算级别比较低

#include<iostream>
#include<cstdio>
#include<map>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
#include<set>
#include<string>
#include<queue>
#define inf 1<<28
#define M 100005
#define N 400005
#define Min(a,b) ((a)<(b)?(a):(b))
#define Max(a,b) ((a)>(b)?(a):(b))
#define pb(a) push_back(a)
#define mem(a,b) memset(a,b,sizeof(a))
#define LL long long
#define MOD 1000000007
using namespace std;
map<int,int>mp;
map<int,int>::iterator it;
int n,q;
int prime[N][15]={0},flag[N]={0};
int gcd(int a,int b){
	return b==0?a:gcd(b,a%b);
}
void Prime(){
    for(int i=2;i<N;i++){
        if(flag[i]){continue;}
        prime[i][++prime[i][0]]=i;
        for(int j=2;j*i<N;j++){
           flag[i*j]=1;
            prime[i*j][++prime[i*j][0]]=i;
        }
    }
}
LL ans;
void dfs(int idx,int num,int cnt,int m,int n,int p){
	if(cnt==m){
		int k=n/num;
		if(m&1) ans-=(LL)num*k*(k+1)/2;
		else ans+=(LL)num*k*(k+1)/2;
		return ;
	}
	if(idx>prime[p][0]) return;
	if(num>n) return ;
	dfs(idx+1,num,cnt,m,n,p);
	dfs(idx+1,num*prime[p][idx],cnt+1,m,n,p);
}
LL slove(int n,int p){
	if(n<=0) return 0;
	ans=(LL)n*(n+1)/2;
	for(int i=1;i<=prime[p][0];i++){
	//	cout<<prime[p][i]<<endl;
		dfs(1,1,0,i,n,p);
	}
	return ans;
}
int main(){
	int t;
	scanf("%d",&t);
	Prime();
	while(t--){
		scanf("%d%d",&n,&q);
		mp.clear();
		while(q--){
			int k,x,y,c;
			scanf("%d",&k);
			if(k==1){
				scanf("%d%d%d",&x,&y,&c);
				LL ret=slove(y,c)-slove(x-1,c);
				for(it=mp.begin();it!=mp.end();it++){
					if((*it).first>=x&&y>=(*it).first){				
						if(gcd(c,(*it).first)==1) ret-=(*it).first;	
						if(gcd(c,(*it).second)==1) ret+=(*it).second;
					}
				}
				printf("%I64d\n",ret);;
			}
			else{
				scanf("%d%d",&x,&y);
				mp[x]=y;
			}
		}
	}
	return 0;
}


HDU 2841

还是乘法分解因子问题.

HDU 4135 

这个是很基本的问题,不知道我的代码哪里错了,随机了1W组数据对拍都一样,无语.贴上我的,看哪位路过大神指点.

/* ***********************************************
Author        :bingone
Created Time  :2015-1-27 18:35:05
File Name     :HDU4135.cpp
************************************************ */

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#define eps 1e-8
#define inf 0x3f3f3f3f
#define maxn (10+1000)
#define zero(x) (fabs(x)<eps)
typedef long long ll;
using namespace std;
int N,M,T;
ll A,B,NN,ans;
ll yz[1000005],yznum;
void dfs(int pos,int cnt,ll num){
	if(pos>=yznum) return;
	if(cnt & 1) ans+=B-B/num-A+A/num;
	else ans-=B-B/num-A+A/num;
	for(int i=pos+1;i<yznum;i++)
		dfs(i,cnt+1,num*yz[i]);
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out.txt","w",stdout);
 	scanf("%d",&T);
	for(int cas=1;cas<=T;cas++){
		scanf("%I64d%I64d%I64d",&A,&B,&NN);
		A--;ans=yznum=0;
		for(int i=2;i*i<=NN;i++) if(NN%i==0){
			yz[yznum++]=i;
			while(NN%i==0) NN/=i;
		}
		if(NN>1) yz[yznum++]=NN;
		for(int i=0;i<yznum;i++)
			dfs(i,1,yz[i]);
		printf("Case #%d: %I64d\n",cas,ans);
	}
    return 0;
}

POJ 1091 跳蚤

求1...m中有多少个元组 满足aA+bB...+zZ=1  .正难则反.求不互质数

/* ***********************************************
Author        :bingone
Created Time  :2015-1-26 11:47:59
File Name     :HDU1685.cpp
************************************************ */

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <string>
#include <cmath>
#include <cstdlib>
#include <ctime>
#define eps 1e-8
#define inf 0x3f3f3f3f
#define maxn (10+10000)
#define zero(x) (fabs(x)<eps)
typedef long long ll;
using namespace std;
int N,M,T,CNT;
int num[maxn];
int yz[100],yznum;
void getyz(){
    yznum=0;int t=M;
    for(int i=2;i*i<=t;i++) if(t%i==0){
        yz[yznum++]=i;
        while(t%i==0) t/=i;
    }
    if(t>1) yz[yznum++]=t;
}

int main()
{
	while(~scanf("%d%d",&N,&M)){
        getyz();
        double ans=pow(M,N);
        for(int i=1;i<(1<<yznum);i++){
            int cnt=0, mul=1;
            for(int j=0;j<yznum;j++) if(i&(1<<j))
                cnt++,mul*=yz[j];
            mul=M/mul;
            if(cnt & 1) ans-=pow(mul,N);
            else ans+=pow(mul,N);
        }
        printf("%.0f\n",ans);
	}
    return 0;
}


展开阅读全文

没有更多推荐了,返回首页