欧拉函数(或者容斥)-HDU5514

来源:互联网 发布:淘宝怎么更改支付宝 编辑:程序博客网 时间:2024/05/21 06:24

题目

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5514

题目来源:2015沈阳区域赛,现场A的题,银牌题。

简要题意:n个青蛙在m长的环上从0开始无限跳,每只跳ai远,求所有会被青蛙跳到的格子下标之和。

数据范围:T⩽20;1⩽n⩽104;1⩽m⩽109;ai⩽109

显然很容易看出格子是否被跳和gcd(ai,m)有关。

对于任意一个gcd(ai,m),其[0,m)内的倍数一定会被跳到。

这个命题进行转化就是对于一个格子x,如果存在gcd(ai,m)∣x,那它会被跳到。

由于gcd(x,m)∣m,其规模最大为m的约数。

gcd(ai,m)∣gcd(x,m)是x被跳到的充要条件。

于是我们可以枚举m的约数g,让gcd(x,m)=g的数形成一个集合,通过gcd(ai,m)∣g的存在性来判断该集合的数是否该被加入。

如果加入的话,由欧拉函数就可以得到集合元素的和了。

比x小与之互质的数的和为φ(x)x2(注意是小,不是小于等于,小于等于要特判1,钢霸发现这个点的)。

于是结果为gφ(m/g)m/g2=φ(m/g)m2。

注意g=m的情况需要去掉,因为是[0,m)的。
实现

基本上怎么写都是过,沈阳的时候随手写了发很暴力的欧拉和枚举,都过了。

回来尝试了下分解素因数再枚举约数,结果没有快非常多。

会写欧拉的话实现这道题不是很难,还可以用容斥原理写

#include <iostream>#include <cstdio>#include <cmath>#include <algorithm>#include <cstring>#include <stack>#include <queue>#include <string>#include <vector>#include <set>#include <map>#define pb push_back#define mp make_pair#define all(x) (x).begin(),(x).end()#define sz(x) ((int)(x).size())#define fi first#define se secondusing namespace std;typedef long long LL;typedef vector<int> VI;typedef pair<int,int> PII;LL powmod(LL a,LL b, LL MOD) {LL res=1;a%=MOD;for(;b;b>>=1){if(b&1)res=res*a%MOD;a=a*a%MOD;}return res;}// headconst int N = 1E4+5;int t, n, m, cas = 1;int a[N];LL getPhi(int m) {    int phi = m;    for (int i = 2; i*i <= m; i++) {        if (m % i == 0) {            while (m % i == 0) m /= i;            phi = phi/i*(i-1);        }    }    return m > 1 ? phi/m*(m-1) : phi;}bool ck(int x) {    for (int i = 0; i < n; i++) {        if (x % a[i] == 0) return true;    }    return false;}int main() {    scanf("%d", &t);    while (t--) {        scanf("%d%d", &n, &m);        for (int i = 0; i < n; i++) {            scanf("%d", a+i);            a[i] = __gcd(a[i], m);        }        LL ans = 0;        for (int i = 1; i*i <= m; i++) {            if (m % i) continue;            if (ck(i)) ans += (LL)getPhi(m/i)*m/2;            if (i*i == m || i == 1) continue;            if (ck(m/i)) ans += (LL)getPhi(i)*m/2;        }        printf("Case #%d: %I64d\n", cas++, ans);    }    return 0;}

容斥代码
这里写图片描述

#include <iostream>#include <cstdio>#include <cstring>#include <cstdlib>#include <cmath>#include <vector>#include <queue>#include <algorithm>#include <set>using namespace std;typedef long long LL;typedef unsigned long long ULL;const LL INF = 1e17+5;const LL MAXN = 1e6+5;const int MOD = 1e9+7;const double eps = 1e-7;const double PI = acos(-1);using namespace std;LL a[MAXN], tmp[MAXN];LL vis[MAXN], num[MAXN];///vis:标记可以走哪些因子数 num[i]:第i个因子加了几次。LL GCD(LL a, LL b){    if(b == 0)        return a;    return GCD(b, a%b);}int main(){    LL T;    scanf("%lld",&T);    for(LL cas=1; cas<=T; cas++){        LL n, m, cnt = 0;        scanf("%lld%lld",&n,&m);        for(LL i=1; i*i<=m; i++){            if(m%i==0){                if(i*i != m)                    tmp[cnt++] = m/i;                tmp[cnt++] = i;            }        }        sort(tmp, tmp+cnt);        memset(vis, 0, sizeof(vis));        memset(num, 0, sizeof(num));        for(LL i=0; i<n; i++)            scanf("%lld",&a[i]);        for(LL i=0; i<n; i++){            LL x = GCD(a[i], m);            for(LL j=0; j<cnt-1; j++)///计算到 cnt-1 就行了                if(tmp[j] % x == 0)                    vis[j] = 1;        }        LL ans = 0;        /**考虑每个因子的贡献是:(m/tmp[i])*(m/tmp[i]-1)/2*tmp[i],等差数列求和        首先将因子 tmp[i] 提出来        m/tmp[i]:相当于等差数列的 a1 + an;        m/tmp[i]-1: 这是等差数列的个数        **/        for(LL i=0; i<cnt-1; i++){            if(num[i] != vis[i]){                ///先计算 vis[i] - num[i] == 1 的                ans += (m/tmp[i])*(m/tmp[i]-1)/2*tmp[i]*(vis[i]-num[i]);                ///cout<<"ans = "<<ans<<endl;                LL tp = vis[i] - num[i];                for(LL j=i; j<cnt-1; j++)                    if(tmp[j] % tmp[i] == 0)                        num[j] += tp;            }        }        printf("Case #%lld: %lld\n",cas,ans);    }    return 0;}