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 }

时间: 2024-10-29 19:07:01

codeforces 487C Prefix Product Sequence (模逆元+构造)的相关文章

CodeForces 487C Prefix Product Sequence

题意: 构造一个1~n的排列  使得n个前缀积%n是一个0~n-1的排列 思路: 首先确定n一定放最后  要不然会有%n会有多个0  这时n-1位置的前缀积为(n-1)! 接着讨论n  n为合数时只有n=4有解  因为如果n为合数一定可以拆成p*q的形式  明显pq|(n-1)! 然后构造ai=(i+1)*inv[i]  因为(i+1)*inv[i] == (j+1)*inv[j]时一定有i==j  所以这样构造满足ai是唯一的  也就是说是一个排列 而且这样构造使得前缀积 a1*a2*a3..

[CF 487C Prefix Product Sequence]

题意 将1~n的正整数重排列,使得它的前缀积在模n下形成0~n-1的排列,构造解或说明无解.n≤1E5. 思考 小范围内搜索解,发现n=1,n=4和n为质数时有解. 不难发现,n一定会放在最后,否则会多出很多的0. 1.n≥4且n为合数:由于n能写成pq的形式,其中pq|(n-1)!,因此第n-1的位置上一定为0,故无解. 2.n为质数:按以下方式构造.令答案ai=i*(i-1)-1(若i-1=0,令其逆元为1).则其前缀积为1,2,3,...,n-1,0.由于i*(i-1)-1=j*(j-1)

cf487C Prefix Product Sequence

Consider a sequence [a1,?a2,?... ,?an]. Define its prefix product sequence . Now given n, find a permutation of [1,?2,?...,?n], such that its prefix product sequence is a permutation of [0,?1,?...,?n?-?1]. Input The only input line contains an intege

CodeForces 837F - Prefix Sums | Educational Codeforces Round 26

按tutorial打的我血崩,死活挂第四组- - 思路来自FXXL /* CodeForces 837F - Prefix Sums [ 二分,组合数 ] | Educational Codeforces Round 26 题意: 设定数组 y = f(x) 使得 y[i] = sum(x[j]) (0 <= j < i) 求初始数组 A0 经过多少次 f(x) 后 会有一个元素 大于 k 分析: 考虑 A0 = {1, 0, 0, 0} A1 = {1, 1, 1, 1} -> {C(

Codeforces 374D Inna and Sequence 二分+树状数组

题目链接:点击打开链接 给定n个操作,m长的序列a 下面n个数 if(co>=0)则向字符串添加一个co (开始是空字符串) else 删除字符串中有a的下标的字符 直接在序列上搞,简单模拟 #include<stdio.h> #include<iostream> #include<string.h> #include<set> #include<vector> #include<map> #include<math.h&

同余问题,乘法模逆元【模板】

扩展欧几里得,求一组解x,y,使得gcd(a,b)  = d = a * x + b * y void ExGcd(int a,int b,int &d,int &x,int &y) { if(b == 0) { x = 1; y = 0; d = a; } else { ExGcd(b,a%b,d,y,x); y -= x*(a/b); } } 扩展欧几里得,求所有解x,y,使得c = a * x + b * y bool ModeEqual(int a,int b,int c)

Codeforces 486E LIS of Sequence(线段树+LIS)

题目链接:Codeforces 486E LIS of Sequence 题目大意:给定一个数组,现在要确定每个位置上的数属于哪一种类型. 解题思路:先求出每个位置选的情况下的最长LIS,因为开始的想法,所以求LIS直接用线段树写了,没有改,可以用 log(n)的算法直接求也是可以的.然后在从后向前做一次类似LIS,每次判断A[i]是否小于f[dp[i]+1],这样就可以确定该位 置是否属于LIS序列.然后为第三类的则说明dp[i] = k的只有一个满足. #include <cstdio>

Codeforces 1132A. Regular Bracket Sequence

原题链接:Codeforces 1132A. Regular Bracket Sequence 题目大意:你有\({cnt}_1,{cnt}_2,{cnt}_3,{cnt}_4\)个"((","()",")(","))",问能否将这些字符串组成一个合法的括号序列. 题解:这一道题,很明显的\({cnt}_2\)是不需要管的,对于第三种情况,它并不改变左右括号的数量差,只有第一.四情况改变,那么,很明显\({cnt}_1={cn

codeforces 1059C. Sequence Transformation【构造】

题目:戳这里 题意:有1,2,3...n这n个数,求一次这些数的gcd,删去一个数,直到剩下一个数为止.输出这n个gcd的最大字典序. 解题思路:一开始的gcd肯定是1,要让字典序最大,我们可以想到下一个应该是2.这样就要把所有的奇数全给删去,这样就要考虑一个特殊情况,就是把所有奇数删去之后,刚好n==1的时候.因为n==1的话,gcd就是剩下的那个数本身了.因此要特判n==3的情况.其他的时候循环删除奇数的操作.(要不是b题看不懂题意,这次也不会那么惨T T 具体看代码. 1 #include