容斥原理、欧拉定理 题集(由易到难)

目录:

一、计算“闰日”数量

二、求两个矩形的面积并

三、求小于n的数中有多少个数可以被已知集合中的数整除

四、给你A、B、N,让你求出[A,B]区间中与N互质的数字个数

五、求在n*m的棋盘中放置k个棋子的方法,要求棋盘四边都有棋子

六、跳蚤

一、计算“闰日”数量

题源:LightOJ1414

思路:判断好区间端点 然后做简单容斥处理即可
1、如何判断闰年:

if((y%4==0&&y%100!=0)||y%400==0) return true;
else return false;

2、如何处理区间端点:
若左端点是闰年,且左端点向右不包含闰日 则左端点年份++
若右端点是闰年,且右端点向左不包含闰日 则右端点年份- -
则问题转化为求区间内闰年数。
3、如何统计 0~当前年闰日数?
容斥原理前缀和思想。

ans=y/4+y/400-y/100;//你品 你细品

最后上代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<map>
#define ll long long
#define cl(a,b) memset(a,b,sizeof(a))
using namespace std;
map<string,int> mp;
void init() {
	mp["January"]=1,mp["February"]=2,mp["March"]=3;
	mp["April"]=4,mp["May"]=5,mp["June"]=6;
	mp["July"]=7,mp["August"]=8,mp["September"]=9;
	mp["October"]=10,mp["November"]=11,mp["December"]=12;
}
bool judge(int y) { //判断是否为闰年
	if(y%4==0&&y%100!=0||y%400==0) return true;
	return false;
}
int calculate(int year1,int year2) { //计算这两年(闭区间里的闰年数量
	year1--;//用了差分的知识
	int ans=(year2/400+year2/4-year2/100);
	ans-=(year1/400+year1/4-year1/100);
	return ans;
}
int main() {
    init();
	int T;
	scanf("%d",&T);
	for(int k=1;k<=T;k++){
		int ans;
		char month1[30],month2[30];
		int day1,day2,year1,year2;
		scanf("%s%d,%d",month1,&day1,&year1);
		scanf("%s%d,%d",month2,&day2,&year2);
		if(judge(year1)&&mp[month1]>=3) year1++;
		if(judge(year2)&&(mp[month2]==1||(mp[month2]==2&&day2<=28))) year2--;
		ans=calculate(year1,year2);
		printf("Case %d: %d\n",k,ans);
	}
	return 0;
}

二、求两个矩形的面积并

题源:51Nod2488

思路:就像集合一样,A∪B=A+B-A∩B
重点是怎么求A∩B
直接给代码就能明白了 这个题不多说

#include<iostream>
#include<cstdio>
#include<cmath>
#define ll long long
#define inf 0x3f3f3f3f
#define MID (t[k].l+t[k].r)>>1
#define cl(a,b) memset(a,b,sizeof(a))
#define dbg printf("aaa\n")
using namespace std;
//求矩形面积并
//嗷嗷这题很简单啊但是自己非要穷举 我真是服了- -
int cal(int a,int b,int c,int d){
    if(c>=b||a>=d) return 0;
    return min(b,d)-max(a,c);
}
int main() {
    int a,b,c,d,e,f,g,h;
    cin>>a>>b>>c>>d>>e>>f>>g>>h;
    cout<<(c-a)*(d-b)+(g-e)*(h-f)-cal(a,c,e,g)*cal(b,d,f,h)<<endl;
    //system("pause");
	return 0;
}

三、求小于n的数中有多少个数可以(还有题是求不可以)被已知集合中的数整除

题源:HDU1796

题意上面一行写的很明确了,可以看出是容斥。为什么?
因为1~n中的某个数,可能被已知集合中的1个或者2个或者多个数的乘积同时整除,所以我们要 加上(n-1)/奇数个元素的最小公倍数 减去(n-1)/偶数个元素的最小公倍数 最后得到的 结果 就是答案。
问:为什么是最小公倍数而不是集合的数直接相乘?答:因为集合中的数不一定互质,他们的最小公倍数才最合适,而若是把一个大数分解成了质因数(如下题),则就直接用乘积即可!!!!!
若想求1~n中与集合互质的个数呢?只要用n-1减去结果 就是答案 例题:UVA10325

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
#define cl(a,b) memset(a,b,sizeof(a))
using namespace std;
//HDU1796
int n,m;
ll num[30];
ll cnt,ans;
ll gcd(ll a,ll b){
	if(b==0) return a;
	return gcd(b,a%b);
}
ll Lcm(ll a,ll b) {
	return a/gcd(a,b)*b;
}
void dfs(int p,ll lcm,int depth) {
	if(depth&1) ans+=(n-1)/lcm;
	else ans-=(n-1)/lcm;
	for(int i=p+1; i<=cnt; i++) {
		ll lcm2=Lcm(lcm,num[i]);
		dfs(i,lcm2,depth+1);
	}
	return;
}
int main() {
	while(cin>>n>>m) {
		ans=0;
		cnt=0;
		cl(num,0);
		for(int i=1; i<=m; i++) {
			int temp;
			cin>>temp;
			if(temp) num[++cnt]=temp;
		}
		for(int i=1; i<=cnt; i++) {
			dfs(i,num[i],1);
		}
		cout<<ans<<endl;
	}
	return 0;
}

四、给你A、B、N,让你求出[A,B]区间中与N互质的数字个数

题源:HDU4135

首先:看到与N互质,想到1~N-1 中与N互质个数,那首先想到欧拉函数!
什么是欧拉函数呢,简言之 phi(n)的值为1~n中与数n 互质的数字个数。
但是这里不是求1~N-1中 而是给定区间A B。
那我们需要怎么求呢?
思路:
1、求出N的所有质因子,存入fac数组中。下面是实现代码:

void get_fac(int n){//求出1~n的质因子  适用于 n数量不是很多 但是n很大 的情况
	//若n数量很多  但是n普遍不大 则适用埃氏筛的方法 顺便求出每个数的质因子
    cnt=0;
    for(int i=2;i*i<=n;i++){
        if(n%i==0){
            fac[++cnt]=i;
            while(n%i==0) n/=i;
        }
    }
    if(n>1) fac[++cnt]=n;//这句很重要 不知道为什么
    return;
}

2、利用前缀和/容斥原理的思想,求出1到A-1中、1到B中 与N不互斥的数量,注意是不互斥,互斥的数量分别用总数减去即可。
3、求2中不互斥数量的方法是,奇数个相乘的时候 加上up/mul 其中up是上界 即A-1或者B,偶数个相乘的时候 减去up/mul 注意这里直接是乘积mul因为相当于上题集合中的数 都互质了 所以乘积就已经是最小公倍数。
4、最后分别用总数减去互斥数量 相减即得最终答案。
下面是AC代码,who 就是fac质因子数组:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ll long long
#define cl(a,b) memset(a,b,sizeof(a))
using namespace std;
int n,cnt;//因子个数
ll a,b,ans;//记录结果
ll who[1010];//记录质因子
void get_who(int n){//求出1~n的质因子
    cnt=0;
    for(int i=2;i*i<=n;i++){
        if(n%i==0){
            who[++cnt]=i;
            while(n%i==0) n/=i;
        }
    }
    if(n>1) who[++cnt]=n;//这句很重要 不知道为什么
    return;
}
void dfs(int p,int num,ll mul,ll up){//p 当前到哪一个了  num 当前一共有多少个 mul 当前乘到多少了
    //奇数个相乘的时候 加上 偶数个相乘 减去
    if(num&1) ans+=up/mul;
    else ans-=up/mul;
    for(int i=p+1;i<=cnt;i++){
        dfs(i,num+1,mul*who[i],up);
    }
}
int main() {
    int T;
    cin>>T;
    for(int t=1;t<=T;t++){
        cin>>a>>b>>n;
        //先求出n的所有质因子存到num里
        get_who(n);
        //先求简单的 用搜索 求1~a中n的因子 倍数 的个数
        ans=0;
        for(int i=1;i<=cnt;i++){
            dfs(i,1,who[i],a-1);
        }
        ll aa=ans;
        ans=0;
        for(int i=1;i<=cnt;i++){
            dfs(i,1,who[i],b);
        }
        ll bb=ans;
        //现在 aa bb 中存的是1~a-1  1~b中 和n不互质的数量
        ll res=b-bb-(a-1-aa);
        cout<<"Case #"<<t<<": "<<res<<endl;
    }
	return 0;
}

五、求在n*m的棋盘中放置k个棋子的方法,要求棋盘四边都有棋子

题源:UVA11806

思路:第一反应显然组合数,但是不会做1551。。。
1、根据m n 先处理出组合数。注意c[n][m]=c[n-1][m]+c[n-1][m-1]
2、根据容斥原理 枚举状态 0000到1111(0到15)代表第一行、第一列、最后一行、最后一列有没有棋子,若有偶数个就加上,奇数个就减去。
3、对于上一条的解释:刚开始没有棋子,0000 所以n 和 m都不会减少 从中选k个 这样 c n*m k 是最大的 然后减去一个的 加上两个的 减去三个的 加上四个的
至于第三条为什么这样 自己还是搞不明白。

下面是AC(copy着写的)代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<stack>
#include<algorithm>
#define ll long long
#define inf 0x3f3f3f3f
#define MID (t[k].l+t[k].r)>>1
#define cl(a,b) memset(a,b,sizeof(a))
#define dbg printf("aaa\n")
using namespace std;
//这是一个容斥问题 可用状压枚举状态
const int maxn=510;//从所有中选出最多五百种嘛
const int mod=1000007;
int n,m,k;
int c[maxn][maxn];
void get_c(){
    //注意公式 c n m= c n-1 m  +  c n-1 m-1  头一次听说来!!!
    cl(c,0);
    for(int i=0;i<=maxn;i++){//至少从0个开始取吧
        c[i][0]=c[i][i]=1;
        for(int j=0;j<i;j++){//c i i 已经初始化了
            c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
        }
    }
    return;
}
int main() {
    get_c();
    int T;
    scanf("%d",&T);
    for(int x=1;x<=T;x++){
        scanf("%d%d%d",&n,&m,&k);
        int sum=0;
        for(int i=0;i<16;i++){//枚举16种状态 0000~1111  0~15
        //代表第一行、最后一行、第一列、最后一列有无棋子
            int a=n,b=m,cnt=0;
            if(i&1){
                --a,++cnt;
            }if(i&(1<<1)){
                --a,++cnt;
            }if(i&(1<<2)){
                --b,++cnt;
            }if(i&(1<<3)){
                --b,++cnt;
            }
            if(cnt&1){//若奇数个
                sum=(sum-c[a*b][k]+mod)%mod;
            }else{
                sum=(sum+c[a*b][k])%mod;
            }
        }
        printf("Case %d: %d\n",x,sum);
    }
	return 0;
}

六、跳蚤

题源:POJ1091

题意:意思是 给你俩数 n m
让你求有多少种组合满足:n个正整数都不大于m 并且满足从这n+1个数中选数能使得a1x1+a2x2+a3*x3+…+a(n+1)*x(n+1)=1 (这是个扩展欧几里得)
易知总共有 m^n种方法 需要减去不行的方法 不懂啊实在不懂 写出来还是不懂
这个题留坑 核心代码阶段还是不懂
核心代码段如下:

if(step>stop){//若之前已集齐
        ll mm=m;
        rep(i,1,stop){//让m除以所有枚举的因子
            mm/=a[i];
        }
        temp+=quick_pow(mm,n);
        return;
    }

附AC代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define ll long long
#define inf 0x3f3f3f3f
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define cl(a,b) memset(a,b,sizeof(a))
using namespace std;
//跳蚤 给你一个卡片 上面n+1个数字 先是n个数字 再是m
//其中n个数字均小于m
//题意转化后 我们要求的是 a1*x1+a2*x2+......+a(n+1)*x(n+1)=1 成立
//这个公式来源于 ax+by=gcd(a,b)  总能找到 x y(可能是负的)使得式子成立
//这里最大公约数是1 所以说就是找出所有互质的组合
//这个不好找 那么我们采用求反的方法 即求出所有非互质的组合
//所以我们需要用容斥原理 奇加偶减 搜索的方法来找出 非互质的组合
//因为可以选重复的 所以在搜索的时候要注意可以用和当前相同的
ll n,m,cnt,temp;//共cnt位
ll num[1010];//获得m的所有因子
ll a[1010];//记录当前所用的所有因子
ll quick_pow(ll a,ll b){
    ll ans=1;
    while(b){
        if(b&1ll) ans*=a;
        a*=a;
        b>>=1;
    }
    return ans;
}
void get_num(){
    cnt=0;
    ll mm=m;
    for(int i=2;i*i<=mm;i++){//从2开始!!!
        if(mm%i==0){
            num[++cnt]=i;
            while(mm%i==0) mm/=i;
        }
    }
    if(mm>1) num[++cnt]=mm;
    return;
}
void dfs(int p,int step,int stop){//p 当前枚举到哪一个 step
    if(step>stop){//若之前已集齐
        ll mm=m;
        rep(i,1,stop){//让m除以所有枚举的因子
            mm/=a[i];
        }//完事m还剩没被枚举到的因子相乘
        //这里还是不太明白!!!拓展欧几里得!!!
        //留坑!!!!
        //留坑!!!!
        temp+=quick_pow(mm,n);
        return;
    }
    rep(i,p,cnt){
        a[step]=num[i];//把当前位置的存上
        dfs(i+1,step+1,stop);
    }
}
int main() {
    cin>>n>>m;
    get_num();
    ll res=quick_pow(m,n),ans=0;
    rep(i,1,cnt){//枚举使用的因子数量
        temp=0;//每次都要置0  因为每次统计temp的加减都不一样!!!
        dfs(1,1,i);
        if(i&1) ans+=temp;//奇数个的时候加上 偶数个的时候减去
        else ans-=temp;
    }
    cout<<res-ans<<endl;
    //system("pause");
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值