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

题目

题目链接: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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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 second
using 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;}
// head
const 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;
}

容斥代码
欧拉函数(或者容斥)-HDU5514_第1张图片

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
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; iscanf("%lld",&a[i]);
        for(LL i=0; ifor(LL j=0; j1; 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; i1; 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 = "<
                LL tp = vis[i] - num[i];
                for(LL j=i; j1; j++)
                    if(tmp[j] % tmp[i] == 0)
                        num[j] += tp;
            }
        }
        printf("Case #%lld: %lld\n",cas,ans);
    }
    return 0;
}

你可能感兴趣的:(数学)