hdu4135 容斥

版权声明:编写不易,转载请注明出处,谢谢。 https://blog.csdn.net/WilliamSun0122/article/details/77161256

题意
题目还是比较好懂的,就是给你一个区间[l,r]和一个数n,问你这个区间中有多少个数和n互质。

题解
容斥的思想还是很好懂的,就是把求集合的并转换成求集合的交。但是用代码表示出来还是有难度的。
这一题给的区间范围还是很大的,我们直接求有多少个数与n互质不是很好求,可以先用补集的思想求有多少个数与n不互质。一个数与n不互质等价于其和n有一个不为1的公因子。此时问题就是求区间[l,r]中有多少个数含有因子x,这个问题也不是很好求,但是求区间[1,m]有多少个数含有因子x就好求了,就等于m/x。所以我们可以先求区间[1,r],再求区间[1,l],最后一减就可以了。
最后还有一个问题就是n的因子会有很多(比如有x,y,z三个因子),区间中有的数可能可能既含有因子x也含有因子y,这样就会重复计数,所以我们要用容斥原理筛去。
具体如何容斥看代码注释。
这里容斥有两种写法,加奇减偶和加偶减奇。(其实本质都是一样的)

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;

ll a,b,totle;
int n,m;
vector<int> f;

ll solve(ll x)  //做容斥
{
    ll ans = 0;  //ans是区间[1,x]中与n不互质的数的个数
    for(ll i=1;i<=totle;i++) //遍历1到所有状态
    {
        //cnt是含有因子的个数,se是含有的因子最后的乘积
        int se=1,cnt=0; 
        for(int j=0;j<m;j++)
        {
            if((i>>j)&1) se *= f[j],cnt++;
        }
        if(cnt&1) ans += x/se;  //加奇
        else ans -= x/se;  //减偶
    }
    //加奇减偶最后求出来的ans就是区间中与n不互质的数的个数
    //再用区间总数减去ans就是区间中与n互质的数的个数
    return x-ans;
}

int main()
{
    int t;
    scanf("%d",&t);
    for(int ca=1;ca<=t;ca++)
    {
        scanf("%lld%lld%d",&a,&b,&n);
        f.clear();
        for(int i=2;i*i<=n;i++)//筛出n的因子
        {
            if(n%i==0)
            {
                f.push_back(i);
                while(n%i==0) n /= i;
            }
        }
        if(n!=1) f.push_back(n);
        m = f.size();
        //用m个二进制位表示n因子的状态,比如n有两个因子2,3
        //那么0就是00代表两个因子都不含有
        //1就是01代表含有3不含2
        //2就是10代表含有2不含3
        //3就是11代表2,3都含有
        totle = (1<<m)-1;
        printf("Case #%d: %lld\n",ca,solve(b)-solve(a-1));
    }
    return 0;
}
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
typedef long long ll;

ll a,b,totle;
int n,m;
vector<int> f;

ll solve(ll x)
{
    ll ans = 0;  //ans求出的就是区间中与n互质的数的个数
    //这里与上一个不同的是从0开始遍历所有状态
    for(ll i=0;i<=totle;i++) 
    {
        int se=1,cnt=0;
        for(int j=0;j<m;j++)
        {
            if((i>>j)&1) se *= f[j],cnt++;
        }
        if(cnt&1) ans -= x/se;  //减奇
        else ans += x/se;   //加偶
    }
    return ans;
}

int main()
{
    int t;
    scanf("%d",&t);
    for(int ca=1;ca<=t;ca++)
    {
        scanf("%lld%lld%d",&a,&b,&n);
        f.clear();
        for(int i=2;i*i<=n;i++)
        {
            if(n%i==0)
            {
                f.push_back(i);
                while(n%i==0) n /= i;
            }
        }
        if(n!=1) f.push_back(n);
        m = f.size();
        totle = (1<<m)-1;
        printf("Case #%d: %lld\n",ca,solve(b)-solve(a-1));
    }
    return 0;
}

上面两种代码,可以说本质上是一样的。就是减奇加偶的从0开始遍历所有状态,其中的0状态就代表了加奇减偶中的区间总数。

猜你喜欢

转载自blog.csdn.net/WilliamSun0122/article/details/77161256