[数论专题]容斥原理练习(持续更新)

参考大佬博客:https://www.cnblogs.com/linyujun/p/5210410.html

目录

  • HDU - 1465 不容易系列之一
  • UVALive - 7040 Color
  • HDU - 4135 Co-prime
  • HDU - 1695 GCD

HDU - 1465 不容易系列之一

这道应该第一反应是排错问题,可以用排错问题的公式。但是,也可以用容斥原理来想。
总的方案数为n!。
假设一定有1封信(指定的,非任意)放对,则有 ( n − 1 ) ! (n-1)! (n1)!种方案。
假设一定有2封信(指定的,非任意)放对,则有 ( n − 2 ) ! (n-2)! (n2)!种方案。
假设一定有3封信(指定的,非任意)放对,则有 ( n − 3 ) ! (n-3)! (n3)!种方案。
……
假设一定有n封信放对,则有 0 ! 0! 0!种方案。
之后,我们给每一项乘上组合数,就可以变成任意的1、2、……、n封信。这时候,会想到全部加起来就是不合理方案数了,但是,这里面其实有重复,所以我们需要去重。
参考wiki:https://oi-wiki.org/math/inclusion-exclusion-principle/
C n 1 ( n − 1 ) ! C^1_n(n-1)! Cn1(n1)!可以理解为上面wiki中的|A|+|B|+|C|……,也就是A信件放对、B信件放对、C信件放对……的情况总和,但是再A信件放对的情况中,也含有B信件放对的情况。所以,我们需要减去的是A和B信件都放对的情况,也就是(n-2)!,相同的其他的C、D等的和A组合,以及他们之间的组合都要算进去,因此就变成了要 − C n 2 ( n − 2 ) ! -C^2_n(n-2)! Cn2(n2)!,但是这减去的里面,又有A、B、C放对的情况被多减去了一次,因此,需要 + C n 3 ( n − 3 ) ! +C^3_n(n-3)! +Cn3(n3)!补上,然后又重复了Orz……一直套娃到最后,结果不符合题意的结果就是:
∑ 1 n ( − 1 ) n − 1 C n i ( n − i ) ! \sum_1^n(-1)^{n-1}C_n^i(n-i)! 1n(1)n1Cni(ni)!
容斥一下,结果就是 n ! − ∑ 1 n ( − 1 ) n − 1 C n i ( n − i ) ! n!-\sum_1^n(-1)^{n-1}C_n^i(n-i)! n!1n(1)n1Cni(ni)!
编程实现就打表套就是了。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
#define inf 0x3f3f3f3f
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define rep(i, a, n) for(register int i = a; i <= n; ++ i)
#define per(i, a, n) for(register int i = n; i >= a; -- i)
#define ONLINE_JUDGE
using namespace std;
typedef long long ll;
const int mod=1e9+7;
template<typename T>void write(T x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x>9)
    {
        write(x/10);
    }
    putchar(x%10+'0');
}
 
template<typename T> void read(T &x)
{
    x = 0;char ch = getchar();ll f = 1;
    while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
    while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;};
ll ksm(ll a,ll n){//看是否要mod 
	ll ans=1;
	while(n){
		if(n&1) ans=(ans*a)%mod;
		a=a*a%mod;
		n>>=1;
	}
	return ans%mod;
}
//==============================================================
ll fac[25];
void init(){
    fac[0]=1;
    rep(i,1,20){
        fac[i]=fac[i-1]*i*1LL;
    }
}
int n;

int main()
{
	#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	//===========================================================
    init();
    while (~scanf("%d",&n))
    {
        ll ans=0;
        int sig=1;
        rep(i,2,n){
            ans+=sig*(fac[n]/fac[i]);
            sig*=-1;
        }
        write(ans),putchar('\n');
    }
    
	//===========================================================
	return 0;
}

UVALive - 7040 Color

从m种颜色挑k种,组合数 C m k C^k_m Cmk没毛病。由于一定要用上k种颜色,直接求是不行的,考虑容斥。若不考虑全部用上,则有 C n k k ∗ ( k − 1 ) n − 1 C^k_nk*(k-1)^{n-1} Cnkk(k1)n1种方案,需要去除不合理方案。这里不合理的方案是只使用了k-1、k-2、……、1种颜色,若只使用了其中的k-1种,则方案数为 C k k − 1 ∗ ( k − 1 ) ∗ ( k − 2 ) n − 1 C_k^{k-1}*(k-1)*(k-2)^{n-1} Ckk1(k1)(k2)n1,和上面一样,有多减去的,所以,需要补上,套娃,……最后结果为:
C m k ∗ ( k ∗ ( k − 1 ) n − 1 − ∑ i = 1 k ( − 1 ) i C k i ( k − i ) ∗ ( k − i − 1 ) n − 1 ) C^k_m*(k*(k-1)^{n-1}-\sum_{i=1}^k(-1)^iC_k^i(k-i)*(k-i-1)^{n-1}) Cmk(k(k1)n1i=1k(1)iCki(ki)(ki1)n1),由于m的范围较大,所以只能直接算。而内层的 C k i C_k^i Cki可以打表解决。
注意:因为有减法,所以需要+mod之后再%mod

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
#define inf 0x3f3f3f3f
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define rep(i, a, n) for(register ll i = a; i <= n; ++ i)
#define per(i, a, n) for(register ll i = n; i >= a; -- i)
#define ONLINE_JUDGE
using namespace std;
typedef long long ll;
const int mod=1e9+7;
template<typename T>void write(T x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x>9)
    {
        write(x/10);
    }
    putchar(x%10+'0');
}
 
template<typename T> void read(T &x)
{
    x = 0;char ch = getchar();ll f = 1;
    while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
    while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;};
ll ksm(ll a,ll n){//看是否要mod 
	ll ans=1;
	while(n){
		if(n&1) ans=(ans*a)%mod;
		a=a*a%mod;
		n>>=1;
	}
	return ans%mod;
}
//==============================================================
#define int ll

ll t;
const int maxn=1e6+10;
ll fac[maxn];
ll finv[maxn];
void init(){//阶乘和阶乘逆元打表
    int lim=1e6+5;
    fac[0]=1;
    rep(i,1,lim){
        fac[i]=(fac[i-1]*i)%mod;
    }
    finv[lim]=ksm(fac[lim],mod-2)%mod;
    per(i,0,lim-1){
        finv[i]=((finv[i+1]%mod)*((i+1)%mod))%mod;
    }
}
ll n,m,k;
inline ll C(ll n,ll m){//O(m)求大范围组合数
    ll t1=1;
    rep(i,n-m+1,n){
        t1=(t1*i)%mod;
    }
    return ((t1%mod)*(finv[m]%mod))%mod;
}
inline ll c(ll n,ll m){//O(1)求小范围组合数
    if(m<0||m>n) return 0;
    return ((fac[n]*finv[n-m])%mod*finv[m])%mod;
}

signed main()
{
	#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	//===========================================================
	read(t);
    ll kase=0;
    init();
    while(t--){
        read(n),read(m),read(k);
        ll ans=k*ksm(k-1,n-1)%mod;
        ll sig=-1;
        rep(i,1,k-1){
            //cout<
            ans=(ans+sig*(c(k,k-i)*(k-i)%mod*(ksm(k-i-1,n-1)%mod)%mod)%mod+mod)%mod;//代式子
            sig=-sig;
        }
        ans=(C(m,k)%mod*ans+mod)%mod;
        printf("Case #%d: %lld\n",++kase,(ans%mod));
    }
	//===========================================================
	return 0;
}

HDU - 4135 Co-prime

这道题涉及到素数分解和容斥原理的实现。
听说容斥原理的实现分为dfs、队列数组和二进制,这我还不是很了解。这里就用了队列数组。
原题目是求[a,b]范围内和n互质的数的数目,我们可以考虑容斥,求不和n互质的。如果直接对[a,b]范围是比较麻烦的,所以考虑用前缀和思想:[1,a-1]范围和[1,b]范围,然后两个一减就好了。
对于一个数x,求[1,x]范围内和n不互质的数:
可以把n进行素数分解为p1、p2、……、pn,然后,求再[1,x]范围内,p1、p2、……、pn的倍数的个数。因为起点是1,所以,数目就是x/p1、x/p2、x/p3、……、x/pn。和上面一样,减去x/p1和x/p2的时候会有重复,所以,还是套娃Orz。关于+和-的处理,可以采用上面说到的队列数组。具体看代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
#define inf 0x3f3f3f3f
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define rep(i, a, n) for(register ll i = a; i <= n; ++ i)
#define per(i, a, n) for(register ll i = n; i >= a; -- i)
#define ONLINE_JUDGE
using namespace std;
typedef long long ll;
const int mod=1e9+7;
template<typename T>void write(T x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x>9)
    {
        write(x/10);
    }
    putchar(x%10+'0');
}
 
template<typename T> void read(T &x)
{
    x = 0;char ch = getchar();ll f = 1;
    while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
    while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;};
ll ksm(ll a,ll n){//看是否要mod 
	ll ans=1;
	while(n){
		if(n&1) ans=(ans*a)%mod;
		a=a*a%mod;
		n>>=1;
	}
	return ans%mod;
}
//==============================================================
const int maxn=1e6+10;
ll a,b,n,t;
ll primes[maxn],que[maxn];
ll front,tot;
void init(){
	memset(primes,0,sizeof(primes));
	memset(que,0,sizeof(que));
	front=0,tot=0;
	for(int i=2;i*i<=n;++i){//对n素数分解
		if(n%i==0){
			primes[tot++]=i;
			while(n%i==0) n/=i;
		}
	}
	if(n!=1) primes[tot++]=n;//这步不要忘记
	que[front++]=-1;
	rep(i,0,tot-1){//队列数组建立
		ll k=front;
		for(ll j=0;j<k;++j){
			que[front++]=que[j]*primes[i]*(-1);
		}
	}
}

ll solve(){//求不和n互质的,容斥原理
	ll res1=0,res2=0;
	rep(i,1,front-1){
		res1+=(a-1)/que[i];
	}
	rep(i,1,front-1){
		res2+=b/que[i];
	}
	//cerr<
	return res2-res1;
}

int main()
{
	#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	//===========================================================
	read(t);
	ll kase=0;
	while(t--){
		read(a),read(b),read(n);
		init();//对n进行素数分解。
		printf("Case #%lld: %lld\n",++kase,(b-a+1)-solve());//容斥求对立事件
	}
	//===========================================================
	return 0;
}

HDU - 1695 GCD

听说这道题的正解的莫比乌斯反演,但容斥也能做,不过估计是数据水卡过去的。
原题是:再[1,b]和[1,d]区间内,分别找两个数x,y,使得gcd(x,y)=k,问一共有多少对x、y满足条件。根据gcd形式:
gcd(x,y)=k,则gcd(x/k,y/k)=1,证明可以素数分解看下。
然后,问题就变成了再[1,b/k]和[1,d/k]范围内分别找两个数p,q,使得p、q互质。
根据上一道题的经验,直接求素数比较难,所以,求个对立事件,不互质的数。
这里我们先假设d/k>b/k,题目给的不是就swap一下。
枚举x,y两个肯定不行,但我们可以枚举其中一个,枚举y。对于y,我们还需要分类讨论:
若y<=b/k,就是我们选定y为一个定值 y i y_i yi,那么,我们要求和几个x(的范围为[1,b/k])和他互质,其实就相当于求在<= y i y_i yi范围内有几个数和yi互质(为什么不考虑 [ y i , b / k ] [y_i,b/k] [yi,b/k]范围的呢?是为了避免等会再枚举 y j y_j yj( y i y_i yi< y j y_j yj<=d/k)的时候和现在的重复)。可以用欧拉函数求前缀和。
若y>b/k,我们换个角度看问题:就是假设现在y= y k y_k yk( y k > b / k y_k>b/k yk>b/k),求在b/k范围内有几个数和它互质,就和第三题一样了。
这里有个小优化,欧拉函数需要打素数表,所以,我们在分解素数的时候可以利用素数表来分解。
另外,注意特判。

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include  
#include 
#define int ll
#define inf 0x3f3f3f3f
#define IOS ios_base::sync_with_stdio(0); cin.tie(0);
#define rep(i, a, n) for(register ll i = a; i <= n; ++ i)
#define per(i, a, n) for(register ll i = n; i >= a; -- i)
#define ONLINE_JUDGE
using namespace std;
typedef long long ll;
const int mod=1e9+7;
template<typename T>void write(T x)
{
    if(x<0)
    {
        putchar('-');
        x=-x;
    }
    if(x>9)
    {
        write(x/10);
    }
    putchar(x%10+'0');
}
 
template<typename T> void read(T &x)
{
    x = 0;char ch = getchar();ll f = 1;
    while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
    while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll lcm(ll a,ll b){return a/gcd(a,b)*b;};
ll ksm(ll a,ll n){//看是否要mod 
	ll ans=1;
	while(n){
		if(n&1) ans=(ans*a)%mod;
		a=a*a%mod;
		n>>=1;
	}
	return ans%mod;
}
//==============================================================
const int maxn=1e5+100;
vector<ll> factor;
ll prime[maxn],tot;
ll vis[maxn];
ll eular[maxn];
ll t;
ll a,b,c,d,k;
inline void init(){//欧拉函数加前缀和
    eular[1]=1;
    for(ll i=2;i<maxn;i++){
        if(!vis[i]) prime[tot++]=i,eular[i]=i-1;
        for(int j=0;j<tot&&prime[j]*i<maxn;j++){
            vis[i*prime[j]]=1;
            if(i%prime[j]==0){
                eular[i*prime[j]]=eular[i]*prime[j];
                break;
            }
            else{
                eular[i*prime[j]]=eular[i]*(prime[j]-1);
            }
        }
    }
    rep(i,1,maxn-1){
        eular[i]+=eular[i-1];
    }
}
ll que[maxn],front;
void predeal(ll n){//分解素数,创建队列数组
    factor.clear();
	//memset(que,0,sizeof(que));
	front=0;
	for(int i=0;prime[i]<=n/prime[i];++i){
        if(n%prime[i]==0){
            factor.push_back(prime[i]);
            while(n%prime[i]==0) n/=prime[i];
        }
    }
	if(n!=1) factor.push_back(n);
	que[front++]=-1;
	rep(i,0,factor.size()-1){
		ll k=front;
		for(ll j=0;j<k;++j){
			que[front++]=que[j]*factor[i]*(-1);
		}
	}
}
inline ll solve(ll b,ll x){//求不互质的数
    predeal(x);
    ll res=0;
    rep(i,1,front-1){
        res+=b/que[i];
    }
    //cerr<
    return b-res;
}

signed main()
{
	#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
	#endif
	//===========================================================
	read(t);
    int kase=0;
    init();
    while(t--){
        read(a),read(b),read(c),read(d),read(k);
        if(!k||b<k||d<k){
            printf("Case %d: %lld\n",++kase,0);
            continue;
        }
        b/=k,d/=k;
        if(d<b) swap(b,d);
        ll ans=eular[b];
        //cerr<
        rep(i,b+1,d){
            ans+=solve(b,i);
        }
        printf("Case %d: %lld\n",++kase,ans);
    }
	//===========================================================
	return 0;
}

你可能感兴趣的:(数论,组合数学)