poj 2154:
这题和2409 类似,不同之处在于,只考虑旋转,不考虑翻转;
因此相对前面题目应该说是更简单,
但一看数据范围,就不是这么回事了,2409完全可以直接循环处理,
但这题目n最大达100000000,显然会TLE,故需寻求更佳的解决方案。
用欧拉函数进行优化:
旋转:顺时针旋转i格的置换中,循环的个数为gcd(i,n),
每个循环的长度为n/gcd(i,n)。
如果枚举旋转的格数i,复杂度显然较高。有没有好方法呢?
可以不枚举i,反过来枚举L。
由于L|N,枚举了L,再计算有多少个i使得0<=i<=n-1并且L=gcd(i, n)。
即gcd(i,n)=n/L。
不妨设a=n/L=gcd(i, n),
不妨设i=a*t则当且仅当gcd(L,t)=1时
Gcd(i,n)=gcd(a*L,a*t)=a。
因为0<=i<n,所以0<=t<n/a=L.
所以满足这个条件的t的个数为Euler(L).
最后的结果为
phi(L)*N^(N/L-1)之和;
2154 Accepted 164K 1532MS C++ 1012B
我的代码:朴素求欧拉函数
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
using namespace std;
int n,mod;
int phi(int n)
{
int rea=n;
for(int i=2;i*i<=n;i++)
{
if(n%i==0)
rea=rea-rea/i;
while(n%i==0)
{
n/=i;
}
}
if(n>1)
rea=rea-rea/n;
return rea%mod;
}
int quickpow(int m,int n,int k)
{
m=m%k;
int b=1;
while(n>0)
{
if(n&1)
b=(b*m)%k;
n=n>>1;
m=(m*m)%k;
}
return b;
}
int main()
{
int i,j,k,t,ans;
scanf("%d",&t);
while(t--)
{
ans=0;
scanf("%d%d",&n,&mod);
for(i=1;i*i<=n;i++)
{
if(i*i==n)
ans=(ans+phi(i)*quickpow(n,i-1,mod))%mod;
else
if(n%i==0)
{
ans=(ans+phi(i)*quickpow(n,n/i-1,mod)+phi(n/i)*quickpow(n,i-1,mod))%mod;
}
}
printf("%d\n",ans);
}
return 0;
}
网上的代码:素数打表求欧拉函数
Accepted 220K 1172MS C++ 1488B
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define maxn 36000
int n, mod, ans;
int prim[35000];
bool flag[maxn + 20];
void get_prim() {
memset(flag, 0, sizeof (flag));
for (int i = 2; i <= 1000; i++)if (!flag[i])
for (int j = i * i; j <= maxn; j += i)flag[j] = true;
for (int i = 2, k = 0; i <= maxn; i++)
if (!flag[i])prim[k++] = i;
}
int eular(int n) {
int i = 0, ans = 1;
for (i = 0; prim[i] * prim[i] <= n; i++) {
if (n % prim[i] != 0)continue;
ans *= prim[i] - 1;
n /= prim[i];
while (n % prim[i] == 0) {
ans *= prim[i];
n /= prim[i];
}
}
if (n > 1)ans *= n - 1;
return ans % mod;
}
int f(int c, int k, int mod) {
int ans = 1;
c = c % mod;
while (k) {
if (k & 1)ans = (c * ans) % mod;
k >>= 1;
c = (c * c) % mod;
}
return ans;
}
int main() {
get_prim();
int i, T;
scanf("%d", &T);
while (T-- && scanf("%d%d", &n, &mod)) {
ans = 0;
for (i = 1; i * i <= n; i++) {
if (i * i == n)//枚举循环长度l,找出相应的i的个数:gcd(i,n)=n/l.
ans = (ans + f(n, i - 1, mod) * eular(i)) % mod;
else if (n % i == 0)//有长度为l的循环,就会有长度为n/l的循环。
ans = (ans + f(n, n / i - 1, mod) * eular(i) + eular(n / i) * f(n, i - 1, mod)) % mod;
}
printf("%d\n", ans);
}
}