There are mm stones lying on a circle, and nn frogs are jumping over them.
The stones are numbered from 00 to m−1m−1 and the frogs are numbered from 11 to nn. The ii-th frog can jump over exactly aiai stones in a single step, which means from stone j mod mj mod m to stone (j+ai) mod m(j+ai) mod m (since all stones lie on a circle).
All frogs start their jump at stone 00, 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.
Input
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 nn and mm - the number of frogs and stones respectively (1≤n≤10^4, 1≤m≤10^9).
The second line contains nn integers a1,a2,⋯,ana1,a2,⋯,an, where aiai denotes step length of the ii-th frog (1≤ai≤109)(1≤ai≤10^9).
Output
For each test case, you should print first the identifier of the test case and then the sum of all occupied stones' identifiers.
Sample Input
3
2 12
9 10
3 60
22 33 66
9 96
81 40 48 32 64 16 96 42 72
Sample Output
Case #1: 42
Case #2: 1170
Case #3: 1872
本题的大体意思很好搞懂,就是青蛙有一个初始位置0,还有每次能够跳多少步,青蛙跳的位置是一个圆形的长度已知,我们需要求青蛙能够落到的位置和。
简单的分析第一个样例:
青蛙每次跳9个位置,圆形跑道长为12。则青蛙的能到达的位置为 9、9*2%12=6、9*3%12=3、9*4%12=0、9*5%12=9......循环往复。
青蛙每次跳10个位置,那青蛙能到达的位置为10、10*2%12=8、10*3%12=6、10*4%12=4、10*5%12=2、10*6%12=0、10*7%12=10....循环往复。
去掉重复的部分则我们知道青蛙可以到达2、3、4、6、8、9、10,答案是42。
通过观察我们知道青蛙每次跳的步数与圆形跑道的长度求一下gcd,只要gcd倍数的位置青蛙都可以跳到。然后我们可以通过等差数列求和公式算出青蛙所有能跳到的点的位置和。但是这样我们会重复计算一部分,例如gcd为2和gcd为3的都会把gcd为6算上,这样就等于把gcd为6的算了两遍。所以我们想到用容斥。
数据量可以看出有n个gcd(n<=10^4),我们枚举取或不不取的时间复杂度为2^n.所以我们肯定不能用常规的方法去求解。
这道题我们有两道写法,我们先看比较传统的吧。
容斥:
我们能够知道每个数的gcd都是他的因子。对于每一个因子我们只需要统计一下它被计算了多少次。我们先给那些因子是所求gcd的倍数的出现次数设为1。然后对所有因子进行容斥。我们记录两个变量,分别为它需要出现的次数和它已经出现的次数。然后通过两个做差可以知道对于这个因子需要出现的次数。对于一个gcd确定的数列,我们可以将其缩小gcd倍使其变为1、2、3、4、5........,然后通过等差数列求和如下:((m-1)/gcd)*((m-1)/gcd+1))*gcd/2。
具体代码如下:
#include<bits/stdc++.h>
const int maxn = 1e4+5;
typedef long long ll;
using namespace std;
ll gcd(ll a,ll b)
{
if(b==0) return a;
return gcd(b,a%b);
}
ll g[maxn],yy[maxn],app[maxn],v[maxn];
int main()
{
int t,cnt,cas;
ll n,m,val,res;
bool ok;
scanf("%d",&t);
cas=1;
while(t--)
{
memset(app,0,sizeof(app));
memset(v,0,sizeof(v));
cnt=0;
res=0;
ok = false;
scanf("%lld %lld",&n,&m);
for(int i=1;i*i<=m;i++)
{
if(i*i==m) yy[cnt++] = i;
else if(m%i==0)
{
yy[cnt++] = i;
yy[cnt++] = m/i;
}
}
sort(yy,yy+cnt);
cnt--;
for(int i=0;i<n;i++)
{
scanf("%lld",&val);
g[i]=gcd(m,val);
if(g[i]==1)
{
ok = true;
res = m*(m-1)/2;
}
for(int j=0;j<cnt;j++)
if(yy[j]%g[i]==0)
v[j]=1;
}
// for(int i=0;i<cnt;i++)
// printf("%lld\n",v[i]);
if(!ok)
{
for(int i=0;i<cnt;i++)
{
if(v[i]!=app[i])
{
ll tt = (m-1)/yy[i];
res+=tt*(tt+1)*yy[i]/2*(v[i]-app[i]);
}
for(int j=i+1;j<cnt;j++)
{
if(yy[j]%yy[i]==0) app[j]+=v[i]-app[i];
}
// printf("%d %lld %lld\n",i,v[i],app[i]);
}
}
printf("Case #%d: %lld\n",cas++,res);
}
return 0;
}
还有一个方案是通过欧拉函数巧妙地解决这类问题
例如第一个样例:
我们对12进行因子分解:12的因子有 2、3、4、6.
我们可以发现 这两只青蛙可以跳的分别为2和3的倍数。
我们记录下m的那些因子是2和3的倍数。在12以内我们可以将其分成:
2、10
3、9
4、8
6 这四组数。我们可以通过phi(x)*x/2求出x范围内和x互质数的和。第一组因为第一组和m的gcd为2所以我们可以看成为6之内的和6互质的数的和,在将其的和扩大gcd倍,phi(6)*6/2 *2 = 12.第二组所有数和m的gcd为3.所以我们可以将其看成4以内的和4互质的和。在将其扩大gcd倍,phi(4)*4/2*3=12.第三组所有数和m的gcd为4,所以我们将其看成3以内和3互质的的和。在将其扩大gcd倍。phi(3)*3/2*4=12. 第四组与m的gcd为6,我们可以将其看作2内的所有数与2互质的数之和。phi(2)*2/2*6=6.
所有值加和则为正确答案。
我们可以看出化简后的规律为: 例如gcd为g的组,我们可以将其写作phi(m/g)*(m/g)/2*g.
代码如下:
#include<bits/stdc++.h>
const int maxn = 1e4+5;
typedef long long ll;
using namespace std;
ll gcd(ll a,ll b)
{
if(b==0) return a;
return gcd(b,a%b);
}
ll getphi(ll x)
{
ll phi =x;
for(int i=2;i*i<=x;i++){
if(x%i==0){
while(x%i==0) x/=i;
phi = phi/i*(i-1);
}
}
if(x>1) {
phi=phi/x*(x-1);
}
return phi;
}
ll g[maxn],yy[maxn];
int main()
{
int t,cnt,cas;
ll n,m,val,res;
bool ok;
scanf("%d",&t);
cas=1;
while(t--)
{
cnt=0;
res=0;
ok = false;
scanf("%lld %lld",&n,&m);
for(int i=2;i*i<=m;i++)
{
if(i*i==m) yy[cnt++] = i;
else if(m%i==0)
{
yy[cnt++] = i;
yy[cnt++] = m/i;
}
}
for(int i=0;i<n;i++)
{
scanf("%lld",&val);
g[i]=gcd(val,m);
if(g[i]==1)
{
ok=true;
res=m*(m-1)/2;
}
}
if(!ok)
{
sort(g,g+n);
for(int i=0;i<cnt;i++)
{
for(int j=0;j<n;j++)
if(yy[i]%g[j]==0)
{
res+=getphi(m/yy[i])*m/2;
break;
}
}
}
printf("Case #%d: %lld\n",cas++,res);
}
return 0;
}