codeforces 487C Prefix Product Sequence (模逆元+构造)
转自http://blog.csdn.net/houserabbit/article/details/41513745
题解写的真棒。。
题目链接:http://codeforces.com/problemset/problem/487/C
题目大意:构造一个1~n的排列 使得n个前缀积对n取余是一个0~n-1的排列
题目分析:好题,首先我们通过简单的分析可以得到n肯定是最后一个数,因为如果n在前面,前缀积肯定不止1个是n的倍数,也就是说对n取模至少有两个0,显然不满足排列,也就是说取模得到排列最后一个数是0,再来考虑前n-1个数,如果就是1 2 3 4...n-1是不是满足条件呢,显然第一个数就让它是1,是始终正确的,我们已经构造出来两个数了再来看中间的,对于一组序列a1 a2 a3 a4 ... an-1,a1=1,如果a2对n取完模要前缀积及a1*a2*a3对n取模的值与a1*a2的不同,我们不妨在a3中把a2对前缀积的影响消除,后面以此类推,这时就要用到模逆元的概念。
对于正整数和,如果有,那么把这个同余方程中的最小正整数解叫做模的逆元。如果为素数,那么还可以根据费马小定理得到逆元为。推导如下:
回到本题,我们可以构造ai=(i+1)*inv[i],(inv[i]表示i的模逆元)
这样得到的前缀积a1a2a3...an-1=1*2*inv[1]*3*inv[2]*...*n*inv[n-1]因为inv[2]*2≡1(mod n)相当于前面说的消除了a2对a3的影响进一步分析,如果n是合数,则其必能表示成x*y,这里x,y是除了1和n的其他因子,因此对于前缀积(n-1)!必然能被n整除,这里有个特例4,4可以构造出1 3 2 4,原因很简单,因为4不算最后一项的前缀积为2*3显然6不能被4整除,但是比4大的最小的合数为6,6就不满足了,因为5!=2*3*4*5显然是6的倍数,当n不断扩大的时候,因子越来越多则更加能够被n整除,所以我们得到除了4以外的其他合数都无法构造出这样的序列,接下来就是怎样求模逆元的问题。
根据上面的结论了,可以用费马小定理通过快速幂取模求解模逆元,不过还有更简便的递推式
inv[i]= (n-n/i)*inv[n%i]%n,初始值inv[1]=1这个递推式的推导如下
a=n/i
b=n%i =>
a*i+b≡0(mod n) (整除部分+余数部分就等于n) =>
-a*i≡b(mod n) (两边同除b*i) =>
-a*inv[b]≡inv[i](mod n) (将a,b替换) =>
-(n/i)*inv[n%i]≡inv[i](mod n) (将左边变为大于0的数,直接加上n即可,因为这时nmodn=0) =>
(n-n/i)*inv[n%i]≡inv[i](mod n) =>
inv[i] = (n-n/i)*inv[n%i]%n
下面给出两种解法:
1.递推式:
1 #include <cstdio> 2 #include <cmath> 3 #define ll long long 4 int const MAX = 1e5 + 10; 5 ll inv[MAX]; 6 int n; 7 8 bool isprime(int x) 9 { 10 if(x == 1) 11 return false; 12 if(x == 2) 13 return true; 14 if(x % 2 == 0) 15 return false; 16 for(int i = 3; i <= sqrt(n); i += 2) 17 if(n % i == 0) 18 return false; 19 return true; 20 } 21 22 int main() 23 { 24 scanf("%d", &n); 25 if(n == 4) 26 { 27 printf("YES\n1\n3\n2\n4\n"); 28 return 0; 29 } 30 else if(n == 1) 31 { 32 printf("YES\n1\n"); 33 return 0; 34 } 35 else if(!isprime(n)) 36 { 37 printf("NO\n"); 38 return 0; 39 } 40 printf("YES\n1\n"); 41 inv[1] = 1; 42 for(int i = 2; i < n; i++) 43 { 44 inv[i] = (n - n / i) * inv[n % i] % n; 45 printf("%lld\n", ((ll) i * inv[i - 1] % n)); 46 } 47 printf("%d\n", n); 48 }
2.费马小定理+快速幂取模 (速度是第1种解法的20倍)
1 #include <cstdio> 2 #include <cmath> 3 #define ll long long 4 int const MAX = 1e5 + 10; 5 ll inv[MAX]; 6 int n; 7 8 bool isprime(int x) 9 { 10 if(x == 1) 11 return false; 12 if(x == 2) 13 return true; 14 if(x % 2 == 0) 15 return false; 16 for(int i = 3; i <= sqrt(n); i += 2) 17 if(n % i == 0) 18 return false; 19 return true; 20 } 21 22 ll multi(ll a, ll b) 23 { 24 ll ans = 0; 25 a %= n; 26 while(b) 27 { 28 if(b & 1) 29 { 30 ans = (ans + a) % n; 31 b--; 32 } 33 b >>= 1; 34 a = (a + a) % n; 35 } 36 return ans; 37 } 38 39 ll quick_mod(ll a, ll b) 40 { 41 ll ans = 1; 42 a %= n; 43 while(b) 44 { 45 if(b & 1) 46 { 47 ans = multi(ans,a); 48 b--; 49 } 50 b >>= 1; 51 a = multi(a,a); 52 } 53 return ans; 54 } 55 56 int main() 57 { 58 scanf("%d", &n); 59 if(n == 4) 60 { 61 printf("YES\n1\n3\n2\n4\n"); 62 return 0; 63 } 64 else if(n == 1) 65 { 66 printf("YES\n1\n"); 67 return 0; 68 } 69 else if(!isprime(n)) 70 { 71 printf("NO\n"); 72 return 0; 73 } 74 printf("YES\n1\n"); 75 for(int i = 2; i < n; i++) 76 printf("%lld\n", i * quick_mod(i - 1, n - 2) % n); 77 printf("%d\n", n); 78 }