题目链接: 见这里

题意:
考虑一个序列[a1, a2, …, an]。定义其前缀产生的一个序列[a1 mod n, (a1a2) mod n, (a1a2…an mod n)]。
现在给定一个n,你需要找到一个[1, 2, …, n]的一个排列组成的序列,使得按照如上规则产生的序列是[0, 1, …,n-1]组成的一个排列。
如果不存在,输出”NO”,否则输出”YES”,然后输出这个序列。如果存在多种方案,任意输出一个。
解题思路: 下面的解题方法来自一位大牛的博客。ORZ
好题,首先我们通过简单的分析可以得到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以外的其他合数都无法构造出这样的序列,接下来就是怎样求模逆元的问题。
这道题的关键就是如何构造这个序列,对于素数求逆元可以用费马小定理+快速幂取模,当然也可以线形求逆元。具体证明,可以参见刚才的博客链接。

代码如下:(线形递推)

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5+10;
LL inv[maxn];
bool isprime[maxn];
void pre_init(){
    for(int i = 2; i * i < maxn; i++){
        if(!isprime[i]){
            for(int j = i * i; j < maxn; j += i){
                isprime[j] = 1;
            }
        }
    }
}
int main(){
    pre_init();
    int n;
    scanf("%d", &n);
    if(n == 1){
        printf("YES\n");
        printf("1\n");
    }
    else if(n == 4){
        printf("YES\n");
        printf("1\n");
        printf("3\n");
        printf("2\n");
        printf("4\n");
    }
    else if(isprime[n]){
        printf("NO\n");
    }
    else{
        printf("YES\n");
        printf("1\n");
        inv[1] = 1LL;
        for(int i = 2; i < n; i++){
            inv[i] = (n - n / i) * inv[n % i] % n;
            printf("%lld\n", ((i * inv[i - 1]) % n));
        }
        printf("%d\n", n);
    }
    return 0;
}