Frogs
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 3069 Accepted Submission(s): 987
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.
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) .
3 2 12 9 10 3 60 22 33 66 9 96 81 40 48 32 64 16 96 42 72
Case #1: 42 Case #2: 1170 Case #3: 1872
初次写,尝试一下,总有第一次嘛!
题意:有n只青蛙跳圈,m块石头,石头的编号为0-m-1,每次只青蛙每次跳的步数为h步,跳无限次,当一块石头被跳过,这块石头便被占领(不重复记录),求最后跳的石头的编号的和为多少,既求最后跳过的不重复的石头编号为多少。
很显然,如果m跟n小的话,直接暴力大法好就可以解决,但是此处显然不同,所以便需要用的容斥原理。
因为青蛙是跳无限次,所以我们很容易就发现,每次青蛙跳过得编号为k*gcd(h,m){k=0,1,2,3,4,5......}而且k*gcd(h,m)一定为m的因子。所以首先我们用一个tep数组讲所有m的因子存起来。
例如,第一组测试示例: 步数为9的青蛙 每次跳过的是 9、6、3然后重复,同理步数为10的青蛙每次跳过的是10、8、6、4、2重复。
由此我们来慢慢的想,岂不就是求石头编号是gcd(h,m)的倍数即可了,现在我们计算一下每个因子所贡献的编号的和,例如3这个因子所贡献的是3 6 9,3+6+9=3(1+2+3)同理2+4+6+8+10=2(1+2+3+4+5)。很显然是一个等差数列求和公式,所以我们可以推出每个因子贡献的值为tep[i]*(m/h)*(m/h-1)/2;
tep[i]为因子大小、m/h为a1+an,m/h-1为1到n的项数。
接下来就是容斥原理了,首先对于每个青蛙而言,它能到达的位置是它每次能跳的长度gcd(h,m)的倍数。但是对于很多的GCD的时候。可能会多算,,比如2的倍数,3的倍数,此时6的倍数就被多算了。。 其实也就是上边的那个思想用容斥的办法,既然6多算了一次,剪掉一次6的倍数就好。。
代码实现:
#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;
const LL INF = 1e17+5;
const LL MAXN = 1e6+5;
const int MOD = 1e9+7;
LL tep[MAXN];//存m的所有因子
LL num[MAXN];//记录每个因子出现的次数
LL vis[MAXN];//标记是否出现
int GCD(int a,int b){
if(b==0)
return a;
else
return GCD(b,a%b);
}//递归求最大公约数
int main(){
int t;
scanf("%d",&t);
LL l=1;
while(t--){
LL n,m;
int cnt=0;
memset(num,0,sizeof(num));
memset(vis,0,sizeof(vis));
scanf("%lld%lld",&n,&m);
for(int i=1;i*i<=m;i++){
if(m%i==0){
if(i*i!=m)
tep[cnt++]=m/i;
tep[cnt++]=i;
}
}//求出m所有因子
sort(tep,tep+cnt);
for(int i=0;i<n;i++){
LL h;
scanf("%lld",&h);
LL x=GCD(h,m);
for(int j=0;j<cnt;j++){
if(tep[j]%x==0)
vis[j]=1;//说明这个因子的所有都可以被跳到的位置vis[j]标记为1;
}
}
vis[cnt-1]=0;
LL ans=0;
//容斥一波
for(int i=0;i<cnt-1;i++){
if(vis[i]!=num[i]){
ans+=tep[i]*(m/tep[i])*(m/tep[i]-1)/2*(vis[i]-num[i]);//等差数列求和
LL tp=vis[i]-num[i];//很巧妙的一个点利用tp每次对num数组进行更新
for(int j=i;j<cnt;j++)
if(tep[j]%tep[i]==0)
num[j]+=tp;//每次更新tep[j]出现的次数
}
}
printf("Case #%lld: %lld\n",l++,ans);
}
return 0;
}