欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一个你绝对能看懂的二进制枚举(容斥原理)ACM-ICPC 2018 沈阳赛区网络预赛

程序员文章站 2022-03-13 15:46:18
...

今天在打网络icpc选拔赛的时候,遇到了这道题,跟大佬队友学习了一下二进制枚举,正文在下面~

题目如下

一个你绝对能看懂的二进制枚举(容斥原理)ACM-ICPC 2018 沈阳赛区网络预赛
Hint
In the all integers from 11 to 44, 11 and 33 is relatively-prime with the integer 44. So the answer is a[1]+a[3]=14.
样例输入
4 4
样例输出
14
题目来源
ACM-ICPC 2018 沈阳赛区网络预赛

思路
首先我们从a[n]的函数表达式入手,我们可以推导得a[n] = n*(n+1),接下来套一个分解质因数的板子,存入一个数组,然后用二进制枚举来解决问题。

正文

所谓二进制枚举 就是一种暴力的方式,用0,1来代表一个数字存在或不存在。
例如 0101 可以理解为 第一个物品要了,第二个不要,第三个要了,第四个不要。
那么这个和容斥原理有什么关系呢?
我们都知道容斥原理的公式为:
一个你绝对能看懂的二进制枚举(容斥原理)ACM-ICPC 2018 沈阳赛区网络预赛
可能很多刚刚接触的小伙伴不太了解,其实有很多的问题需要容斥来解决,我举一个例子:
如果求100以内的除了 2的倍数 和 3的倍数的数 有多少个,我们可以很容易知道 2的倍数的个数是100/2,而3的倍数是100/3,“答案“是100-50-33 = 17
这显然是不对的 以6为例 6既是2的倍数也是三的倍数,显然减重了。
相信很多人要说啦 啊~ 我暴力行不行 我每个数都判断一下

#include<iostream>
using namespace std;
int main(){
	int ans = 0;
	for(int i = 1;i<+100;i++){
		if(i%2==0 || i%3==0){
			ans++;
		}
	}
	cout<<ans<<endl;
}

当然可以,但是这个时间复杂度是0(n)的,当n为1e9左右的数据就会超时了
那我们应该怎么办呢?(╯‵□′)╯︵┻━┻
这里我先引入容斥原理的概念
我们回到刚才的那个问题,我们有没有办法解决减重的情况?
这里我们就要运用容斥原理解决这个问题了,
100-2的倍数-3的倍数+6的倍数
减重复的加回来就好了,这个6是二和三的最小公倍数
好了这个问题我们已经很好的解决了,但是我们真的解决问题了吗?
我们在举一个例子 :
100以内的数 求 2 3 5 7 11 的倍数,我们还是用刚才方法,
2的倍数+3的倍数+5的倍数+7的倍数+11的倍数- 。。。。。。。
这个时候我们犯难了 要减的有 c(5,2)= 10项 还有加的C(5,3)项。。。。。。。。。。
太麻烦了,明明这么简单的一道题,怎么解决?
这里我们就要引入二进制枚举的概念了:
代码先奉上:

#include<iostream>
#include<stdio.h>
#include<math.h>
using namespace std;
int Gcd(int a,int b){
    return b==0?a:Gcd(b,a%b);
}
int Lcm(int a,int b){
    return a*b/Gcd(a,b);
}
int a[] = {2,3,5,7,11};
int main(){
    int n;
    int lena = 5;
    while(~scanf("%d",&n)){
        int ans = 0;
        for(int i = 0;i<(1<<lena);i++){ 
///每个数都有2种状态 ,在或不在 在本题中带的意思是2,3,5,7,11的倍数或不是,
///一共有2^5种可能
            int tmp  = 0;
            int lcm=1;
            for(int j = 0;j<lena;j++){  ///查看每一位的情况
                if((1<<j)&i){
                    tmp++;         ///记录有几个1
                    lcm*=a[j];
                }
            }
            if(tmp%2==1) ans+=n/lcm; //1的个数是奇数则加 偶数则减
            else ans-=n/lcm;
        }
        cout<<ans+n<<endl;
    }
}


所以,我们可以使用二进制枚举来模拟这个过程。

AC代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<set>
#include<vector>
#include<queue>
#include<cmath>
#include<string>
#include<map>
using namespace std;
#define LL long long
#define N 100005
const LL mod=1e9+7;
const int INF=0x3f3f3f3f;

int n,m;
LL prime[20];
int cnt;
LL ni6,ni2;
LL qpow(LL a,LL k){
    LL tmp=1;
    while(k){
        if(k&1)
            tmp=(tmp*a)%mod;
        a=(a*a)%mod;
        k>>=1;
    }
    return tmp;
}
void fenjie(int x){
    int xx=sqrt(x);
    cnt=0;
    if(x%2==0){
        prime[cnt++]=2;
        while(x%2==0) x/=2;
    }
    int tmp=3;
    while(x!=1 && tmp<=xx){
        if(x%tmp==0){
            prime[cnt++]=tmp;
            while(x%tmp==0) x/=tmp;
        }
        tmp+=2;
    }
    if(x!=1) prime[cnt++]=x;
}

LL SUM(LL k,LL n){
    LL ans=(((((((k*k)%mod*n)%mod*(n+1))%mod)*(2*n+1))%mod)*ni6)%mod+((((k*n)%mod)*(n+1)%mod)*ni2)%mod;
    return ans;
}
void solve(){
    LL ans=SUM(1,n);
    for(int i=0;i<(1LL<<cnt);i++){
        LL x=1;
        LL tmp=0;
        for(int j=0;j<cnt;j++){
            if((1<<j)&i){
                x*=prime[j];
                tmp++;
            }
        }
        if(tmp%2==1)ans=(ans-SUM(x,n/x))%mod;
        else ans=(ans+SUM(x,n/x)+mod)%mod;
    }
    ans=(ans-SUM(1,n)+mod)%mod;
    printf("%lld\n",(ans+mod)%mod);
}
int main()
{
    ni6=qpow(6,mod-2);
    ni2=qpow(2,mod-2);
    while(~scanf("%d%d",&n,&m)){
    fenjie(m);
    solve();
    }
    return 0;
}