HDU 5514 【2015沈阳现场赛 F】 Frogs

http://acm.hdu.edu.cn/showproblem.php?pid=5514
纪念在赛场上逝去的3个小时。。。

题目

N104 个青蛙, M109 个石头, 第 i 只青蛙一次能跳ai109 步,所有青蛙一开始在石头 0 , 可以跳任意次数, 问最后所有被跳过的石头的编号之和。

题解

易知,令ai=gcd(ai,M)不会影响答案
现在即要求: {kai}kMai
做法是:
1. 先标记所有 f[ai]=1
2. 对每个 f[x]=1 ,有 f[x×factM]=1
3. 接下来计算答案:对每个 f[x]=1 x ,要统计出所有满足gcd(k,Mx)=1(kMx) xk(kMx) 的和(这样保证不会重复,即约数 x 对答案单独的贡献)
4. 由定理知gcd(k,N)=1 k 的和为ϕ(n)×n2(因为与 N 互素的数能两两配对和为N,而当 N >2的偶数时, N/2 不与 N 互素,当N=2时特判满足公式)
5. 答案即为

f[x]=1ϕ(Mx)×Mx2

注意:
第二步从小到大枚举 x ,可以讲factM进一步化为 priM ,即 M 的质因子
ϕ()能由积性函数性质快速求出 ϕ(factM)

code

#include <algorithm>
#include <bitset>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <iostream>
#include <map>
#include <queue>
#include <set>
#include <string>
#include <vector>

typedef long long LL;

const int maxn = 10010;

int n, m;
int a[maxn];

int pri[20], pri_N;

void get_pri(int n) {
    for (int p = 2; p*p <= n && n > 1; ++ p) {
        if (n%p == 0) {
            pri[pri_N ++] = p;
            while (n%p == 0) n /= p;
        }
    }
    if (n > 1) pri[pri_N ++] = n;
}
int phi(int n) {
    int res = n;
    for (int p = 2; p*p <= n && n > 1; ++ p) {
        if (n%p == 0) {
            res = res / p * (p-1);
            while (n%p == 0) n /= p;
        }
    }
    if (n > 1) res = res / n * (n-1);
    return res;
}

LL res;

std::set<int> f;
typedef std::set<int>::iterator IT;

int gcd(int a, int b) { while (b) { int c = a; a = b; b = c%b; } return a; }

void solve() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; ++ i) { scanf("%d", &a[i]); a[i] = gcd(a[i], m); }

    pri_N = 0;
    get_pri(m);

    res = 0; f.clear();
    for (int i = 0; i < n; ++ i) f.insert(a[i]);
    for (IT it = f.begin(); it != f.end(); ++ it) {
        for (int i = 0; i < pri_N; ++ i) if (m % ((LL)*it * pri[i]) == 0)
            f.insert((LL)*it * pri[i]);

        LL s = (LL)m / *it * phi(m / *it) / 2;
        res += s * *it;
    }
    printf("%I64d\n", res);
}

int main() {
//  freopen("F.in", "r", stdin);

    int kase, i = 0; scanf("%d", &kase);
    while (kase --) {
        printf("Case #%d: ", ++ i);
        solve();
    }

//  for(;;);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值