poj 2154 Color
题意:有N种颜色,项链上有N颗珠子;(1 <= N <= 1e^9)每种颜色的珠子有无限个;问通过旋转操作有多少种不同的项链;
思路:等价类计数问题 + 欧拉函数优化;
等价类计数:
旋转:旋转i颗珠子的间距;0,i,2i...构成一个循环;这个循环有n/gcd(n,i)颗珠子,总共有gcd(n,i)个循环;所以不动点总数的平均值就为 1/N*Σ(1 <= i <= n) Ngcd(i,n)
优化:朴素算法的时间复杂度是O(n*log(n)),gcd的时间复杂度要小于log(n);实际上我们是枚举了i,通过gcd来求出循环节的个数的;易知循环节的个数d是n的约数(不只是素数),这可以在sqrt(n)内求解;如果我们通过枚举d来求出有多少k满足gcd(k,n) = d (欧拉函数)
gcd(k,n) = d,gcd(k/d,n/d) = 1,(1 <= k <= n) ==>gcd(k,n/d) = 1(1 <= k <= n/d);即 num(k) = Φ(n/d);这样公式就转化为Σd|n[Φ(N/d)*N(d-1)]
那时间复杂度就是sqrt(n)*log(n)*(约数个数);
注:这题在循环终止上要特别注意,否则容易TLE,卡常数。这道题将近1s才过..前几次在phi中卡了常数TLE了;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<iostream> #include<cstdio> #include<cstring> #include<string.h> #include<algorithm> #include<vector> #include<cmath> #include<stdlib.h> #include<time.h> #include<stack> #include<set> #include<map> #include<queue> using namespace std; #define rep0(i,l,r) for(int i = (l);i < (r);i++) #define rep1(i,l,r) for(int i = (l);i <= (r);i++) #define rep_0(i,r,l) for(int i = (r);i > (l);i--) #define rep_1(i,r,l) for(int i = (r);i >= (l);i--) #define MS0(a) memset(a,0,sizeof(a)) #define MS1(a) memset(a,-1,sizeof(a)) #define MSi(a) memset(a,0x3f,sizeof(a)) #define inf 0x3f3f3f3f #define lson l, m, rt << 1 #define rson m+1, r, rt << 1|1 typedef __int64 ll; template<typename T> void read1(T &m) { T x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} m = x*f; } template<typename T> void read2(T &a,T &b){read1(a);read1(b);} template<typename T> void read3(T &a,T &b,T &c){read1(a);read1(b);read1(c);} template<typename T> void out(T a) { if(a>9) out(a/10); putchar(a%10+'0'); } const int MAXN = 5e4; int prime[MAXN],check[MAXN]; void getprime() { int i,j; for(i = 2;i < MAXN;i++){ if(check[i] == 0) prime[++prime[0]] = i; for(j = 1;j <= prime[0] && prime[j] < MAXN/i;j++){ check[prime[j]*i] = 1; if(i%prime[j] == 0) break; } } } int N,p; int phi(int d) { int ans = d,tmp = d; for(int i = 1;prime[i]*prime[i] <= tmp;i++){//i <= prime[0] && prime[i] <= tmp 卡常数 if(tmp%prime[i] == 0) ans -= ans/prime[i]; while(tmp % prime[i] == 0) tmp /= prime[i]; } if(tmp != 1) ans -= ans/tmp; return ans%p; } int pow_mod(int a,int n) { if(n == 0) return 1; if(n == 1) return a; int ans = pow_mod(a,n/2); (ans = ans*ans) %= p; if(n & 1) ans = ans*a%p; return ans; } int main() { int X; read1(X); getprime(); while(X--){ read2(N,p); int ans = 0, tmp = sqrt(N)+0.5; for(int i = 1;i <= tmp;i++){ //只需整除即可,未必是素数; if(N % i) continue; (ans += phi(N/i)*pow_mod(N%p,i-1)) %= p; if(N != i*i) (ans += phi(i)*pow_mod(N%p,N/i-1)) %= p; } out(ans); puts(""); } return 0; }