欧拉函数+容斥

hdu  1695

题目:各处两个区间[1,b],[1,d]和k,取两区间中的元素x、y,使得gcd(x,y)=k;
        问这种组合有多少种,gcd(y,x)=k和gcd(x,y)=k是相同的。
分析:数据量为100,000,呕心的k可以为0。
        由数据可以知道不可以暴力
        (1)由于gcd(x,y)=k 满足 gcd(x/k,y/k)=1(!!)
                所以区间可以缩小为[1/k,b/k],[1/k,d/k]
                问题也转换成为 取两区间中的元素x、y,使得gcd(x,y)=1;
        (2)由于gcd(y,x)=1和gcd(x,y)=1是相同的,所以就有了 x<y,
                于是可以先取小区间进行讨论,由于 gcd(x,y)=1 对于数论来说是
                非常特殊的东西,所以就想到欧拉函数(phi[])(!!),所以小
                区间可以计算为:
                        ans += phi[1]+phi[2]+...+phi[b]
        (3)对于[b/k+1,d/k]的讨论,设y为区间的一个元素,
                则可以对y进行质因数分解(不用保留指数)
                于是得到集合:  {p1, p2, p3 ,...} p为素数(!!)
                虽然是要求 gcd=1 的组合,但反过来也可以求 gcd!=1的组合,
                其实都一样,看个人习惯。于是可以用容斥原理进行统计能被这
                些素数整除的数的个数,记得不可以一个一个来(有重复情况)(!!)
                最后减一下求补数就可以加到ans


                ps:可以会有疑问为什么要分解再统计,我没试过暴力,我想应该会
                因为求gcd时用的时间会很多,然后会造成tle。

看另外那个区间有多少个数与i不互质。

容斥原理的具体如下:

区间中与i不互质的个数 = (区间中i的每个质因数的倍数个数)-(区间中i的每两个质因数乘积的倍数)+(区间中i的每3个质因数的成绩的倍数个数)-(区间中i的每4个质因数的乘积)+...


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define INT long long 
using namespace std;
const int maxn = 100010;
const int Mr = 400;
INT phi[maxn];
bool notp[Mr];
int pr[200], pn;
int a, b, c, d, k;
void getprime() {//线性筛素数
    pn = 0;
    memset(notp, 0, sizeof (notp));
    int i, j;
    for (i = 2; i < Mr; i++) {
        if (!notp[i])pr[pn++] = i;
        for (j = 0; j < pn && pr[j] * i < Mr; j++) {
            notp[pr[j] * i] = 1;
            if (i % pr[j] == 0) break;
        }
    }
}
void init() {
    INT i, j;
    for (i = 1; i <= maxn; i++)phi[i] = i; //线性筛欧拉函数
    for (i = 2; i <= maxn; i += 2) phi[i] /= 2;
    for (i = 3; i <= maxn; i += 2) {
        if (phi[i] == i) {
            for (j = i; j <= maxn; j += i)
                phi[j] = phi[j] / i * (i - 1);
        }
    }
    getprime();
}
int p[100];
int ppn;
int gcd(int x, int y) {
    if (!x || !y) return max(x, y);
    for (int t; t = x % y; x = y, y = t);
    return y;
}
int lcm(int a, int b) {
    return a / gcd(a, b) * b;
}
void fun(int k, int &ans) {
    ans = 1;
    int i;
    int cnt = 0;
    for (i = 0; i < ppn && k > 0; k >>= 1, i++) {
        if (k & 1) {
            ans = lcm(ans, p[i]);
            cnt++;
        }
    }
    if (!(cnt & 1)) ans = -ans; //偶数取反在这里取了!!
}
int calc(int n) {
    int i, j;
    ppn = 0;
    //分解质因数
    for (i = 0; i < pn && pr[i] * pr[i] <= n; i++) {
        if (n % pr[i] == 0) {
            p[ppn++] = pr[i];
            while (n % pr[i] == 0) n /= pr[i];
        }
    }
    if (n != 1) p[ppn++] = n;
    int sum = 0;
    int tmp;
    for (i = 1; i < (1 << ppn); i++) {
        fun(i, tmp); //按二进制计算指定素数的最小公倍数
        tmp = b / tmp; //计算可以被这个数整除的数字个数
        sum += tmp;
    }
    return b - sum; //取反
}
int main() {
    //freopen("in","r",stdin);
    init();
    int T, tt;
    cin >> T;
    for (tt = 1; tt <= T; tt++) {
        scanf("%d%d%d%d%d", &a, &b, &c, &d, &k);
        if (b > d) swap(b, d);
        if (k == 0) {//这个k=0比较无聊,但以后要注意同类型的问题
            printf("Case %d: 0\n", tt);
            continue;
        }
        b /= k;
        d /= k;
        INT ans = 0;
        int i, j;
        for (i = 1; i <= b; i++) {
            ans += phi[i];
        }
        for (i = b + 1; i <= d; i++) {//对每个数字计算
            ans += calc(i);
        }
        printf("Case %d: %I64d\n", tt, ans);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值