题目链接:hdu 5514
题目大意:N只青蛙,M个石头构成一个环,第i只青蛙每一次向后跳Ai个石头,即下一步的位置是(当前位置+Ai) mod m,最开始的时候所有青蛙都在位置0,每只青蛙会跳无数次,问最终所有被青蛙跳过的石头的编号之和是多少?
解题思路:第i只青蛙会踩过的石头编号分别为0,gcd(Ai, m), 2 * gcd(Ai, m) ……一共有m/gcd(Ai,m)-1个,这个的和很是容易用等差数列求和计算出来……但是不同的i跳过的石头有重复! 去重的方法是容斥原理(莫比乌斯我不还不会。。),求出m的所有因数,记录每个gcd(Ai,m)的贡献,一边求和一边除去多算的贡献,用数组c[]记录多算的贡献,v[]记录贡献,详见代码。
ps.1~10^9的所有数中,约数最多的数有1344个约数,所以O(n^2)不会超时。
</pre><pre class="cpp" name="code">#include<stdio.h> #include<algorithm> #include<vector> #include<math.h> #include<string.h> #define ll long long using namespace std; vector<int>ft; int v[2005],c[2005],ca=0, a; void qft(int m) { ft.clear(); int n=(int)sqrt(m)+1; for(int i=1; i<n; i++) { if(!(m%i)) { ft.push_back(i); if(m/i!=i)ft.push_back(m/i); } } } int gcd(int a,int b) { while(a&&b) { if(a>b)a=a%b; else b=b%a; } return a+b; } int find(int m) { return lower_bound(ft.begin(),ft.end(),m)-ft.begin(); } int main() { int t,n,m; scanf("%d",&t); while(t--) { memset(v,0,sizeof(v)); memset(c,0,sizeof(c)); scanf("%d%d",&n,&m); qft(m); sort(ft.begin(),ft.end()); for(int i=0; i<n; i++) { scanf("%d",&a); int d=gcd(a,m); v[find(d)]=1;//c语言函数不能嵌套!否则会tle } int len=ft.size(); for(int i=0; i<len; i++) { if(v[i]) for(int j=i+1; j<len; j++) if(!v[j]&&ft[j]%ft[i]==0)v[j]=1; } ll ans=0; for(int i=0; i<len; i++) { ll x=v[i]-c[i]; if(x==0)continue; ans+=(ll)(m/ft[i]-1)*(m/ft[i])/2*x*ft[i];//小心越界啊大兄弟 for(int j=i; j<len; j++) if(ft[j]%ft[i]==0)c[j]+=x; } printf("Case #%d: %I64d\n",++ca,ans); } }