您的位置:首页 > 其它

HDU 5514 Frogs(欧拉函数+数论YY)

2017-06-12 12:37 387 查看
传送门

Frogs

Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 2198 Accepted Submission(s): 714


[align=left]Problem Description[/align] There are m stones lying on a circle, and n frogs are jumping over them.
The stones are numbered from 0 to m−1 and the frogs are numbered from 1 to n. The i-th frog can jump over exactly ai stones in a single step, which means from stone j mod m to stone (j+ai) mod m (since all stones lie on a circle).

All frogs start their jump at stone 0, then each of them can jump as many steps as he wants. A frog will occupy a stone when he reach it, and he will keep jumping to occupy as much stones as possible. A stone is still considered “occupied" after a frog jumped away.
They would like to know which stones can be occupied by at least one of them. Since there may be too many stones, the frogs only want to know the sum of those stones’ identifiers.
[align=left]Input[/align] There are multiple test cases (no more than 20), and the first line contains an integer t,
meaning the total number of test cases.

For each test case, the first line contains two positive integer n and m - the number of frogs and stones respectively (1≤n≤104, 1≤m≤109).

The second line contains n integers a1,a2,⋯,an, where ai denotes step length of the i-th frog (1≤ai≤109).
[align=left]Output[/align] For each test case, you should print first the identifier of the test case and then the sum of all occupied stones’ identifiers.
[align=left]Sample Input[/align]
3

2 12

9 10

3 60

22 33 66

9 96

81 40 48 32 64 16 96 42 72

[align=left]Sample Output[/align]
Case #1: 42

Case #2: 1170

Case #3: 1872


题目大意:

有 n 个青蛙,第 i 个青蛙每次只能够跳 ai 步,现在有 m 个石头围成一圈,编号为 0 到 m−1,现在青蛙可以围着这个石头组成的圆跳无限次,每跳一次就会占领这个石头,可以无限占领,现在问你的是这 n 个青蛙占领的石头的编号的总和是多少。

解题思路:

首先能够想到的是: 第 i 个青蛙能够跳的步长一定是 gi=GCD(m,ai),所以能占领的石头的编号就是 gi 的倍数,就拿第一个样例来说吧:

2 12
9 10


对于第一只青蛙来说 g1=GCD(12,9)=3,所以步长为 3, 那么他能跳的石头编号就是 0,3,6,9

对于第二只青蛙来说 g2=GCD(12,10)=2,所以步长为 2, 那么他能跳的石头编号就是 0,2,4,6,8,10

显然第一只青蛙和第二只青蛙占领的石头编号有重复的地方,那么我们为了消除这些重复的值,我们规定第 i 个石头只能由 GCD(m,i) 的步长的来占领。那么就有:

2,10 只能由步长为 2 的来占领;

3,9 只能由步长为 3 的来占领;

4,8 只能由步长为 4 的来占领;

6 只能由步长为 6 的来占领;

这样的话,就消除了重复的地方,现在问题就是怎么计算这样的和。

显然这些步长是由 <m 的 m 的因子组成,那么我们首先把这些因子预处理出来,然后判断该因子是否符合条件,即是不是能够被gcd(m,ai) 中的一个整除,在然后我们将这些步长当成公共因子提出来发现:

2+10=2∗(1+5)

3+9=3∗(1+3)

4+8=4∗(1+2)

6=6∗1

对于步长为 x 的求和来说,就是 x∗(与mx互素的个数的和)

需要知道一个结论:

在 [1,x] 中与 x 互素的数的和为: Phi(x)∗x2

那么显然有:

对于步长为 x 的求和来说,x∗Phi(mx)∗mx2

整理得:

Phi(mx)∗m2

所以最终的结果就是: ∑Phi(mx)∗m2

扩展:

对于在 [1,x] 中与 x 互素的数的和为: Phi(x)∗x2 的简单证明:

对于 gcd(x,i)=gcd(x,x−i)

那么就是成对出现的,不会出现相同的结果, 即i=x−i

假设这个成立那么有 x=2∗i, gcd(x,i)=i,与已知矛盾,所以不成立,

那么显然 gcd(x,i)=1 的个数为 Phi(x) 个,然后gcd(x,i)=1 的与 gcd(x,x−i)=1 的合并就有:

与 x 互素的和就是 Phi(x)∗x2

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>

using namespace std;
typedef long long LL;
const LL MAXN = 1e4+5;

LL gcd(LL a, LL b){
if(b == 0) return a;
return gcd(b, a%b);
}
LL a[MAXN], fac[MAXN], g[MAXN];
LL Phi(LL x){
LL ans = x;
for(LL i=2; i*i<=x; i++){
if(x%i==0){
ans -= ans/i;
while(x%i==0) x /= i;
}
}
if(x > 1) ans -= ans/x;
return ans;
}
int main()
{
int T;
scanf("%d", &T);
for(int cas=1; cas<=T; cas++){
int n; LL m; scanf("%d%lld",&n, &m);
int ok = 0;
for(int i=0; i<n; i++){
scanf("%lld",&a[i]);
g[i] = gcd(a[i], m);
if(g[i] == 1) ok = 1;
}
printf("Case #%d: ",cas);
if(ok == 1){
printf("%lld\n",m*(m-1)/2);
continue;
}
sort(g, g+n);
n = unique(g, g+n) - g;
int cnt = 0;
for(LL i=2; i*i<=m; i++){
if(i*i == m) fac[cnt++] = m/i;
else if(m%i==0) fac[cnt++] = i, fac[cnt++] = m/i;
}
sort(fac, fac+cnt);
LL sum = 0;
for(int i=0; i<cnt; i++){
for(int j=0; j<n; j++){
if(fac[i]%g[j] == 0){
sum += Phi(m/fac[i])*m/2;
break;
}
}
}
printf("%lld\n",sum);
}
return 0;
}
/**
2
9 96
81 40 48 32 64 16 96 42 72
*/


补:

现在更新一种容斥原理的做法,最开始的时候,我也想到容斥了,可是后来精度爆炸就没怎么想了,现在补一下:

现在我们已知 gi=gcd(m,ai), 我们还是考虑 m 的因子x,如果这个因子 x 符合条件,那么我们来计算 x 对答案做出的贡献: (mx−1)∗m2。

首先我们预处理出 m 的除了 1 和 m 的因子 fac[i],如果这个因子fac[i]%g[i]=0 的话(其中 g[i]为可能出现的最大公约数),那么我们将其用一个 vis 数组进行标记为 1,然后再用一个数组 num 记录当前因子对答案做过的贡献,初始值为 0,那么有第i 个因子做出的贡献为: (mx−1)∗m2∗(vis[i]−num[i])

因为可能有重复的,所以我们就对所有能够整除因子 fac[i] 的因子全都加上 vis[i]−num[i], 然后就可以计算答案了。

代码:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <algorithm>

using namespace std;
typedef long long LL;
const int MAXN = 1e4+5;
LL gcd(LL a, LL b){
if(b == 0) return a;
return gcd(b, a%b);
}
LL g[MAXN], fac[MAXN];
int tp[MAXN], num[MAXN], vis[MAXN];
int main()
{
int T;
scanf("%d", &T);
for(int cas=1; cas<=T; cas++){
int n; LL m; scanf("%d%lld",&n, &m);
int ok = 0;
for(int i=0; i<n; i++){
scanf("%lld",&g[i]);
g[i] = gcd(g[i], m);
if(g[i] == 1) ok = 1;
}
printf("Case #%d: ",cas);
if(ok == 1){
printf("%lld\n",m*(m-1)/2);
continue;
}
sort(g, g+n);
n = unique(g, g+n) - g;
memset(vis, 0, sizeof(vis));
memset(num, 0, sizeof(num));
int cnt = 0;
for(LL i=2; i*i<=m; i++){
if(i*i == m) fac[cnt++] = m/i;
else if(m%i==0) fac[cnt++] = i, fac[cnt++] = m/i;
}
sort(fac, fac+cnt);
int cnt1 = 0;
for(int i=0; i<n; i++){
if(!vis[i]){
tp[cnt1++] = g[i];
for(int j=0; j<n; j++)
if(g[j]%g[i]==0) vis[j] = 1;
}
}
memset(vis, 0, sizeof(vis));
for(int i=0; i<cnt; i++){
for(int j=0; j<cnt1; j++){
if(fac[i]%tp[j] == 0){
vis[i] = 1;
break;
}
}
}
LL sum = 0;
for(int i=0; i<cnt; i++){
if(num[i] != vis[i]){
sum += m*(m/fac[i]-1)/2*(vis[i]-num[i]);
for(int j=i+1; j<cnt; j++)
if(fac[j]%fac[i] == 0)
num[j] = num[j] + vis[i] - num[i];
}
}
printf("%lld\n",sum);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: