题目链接:
题目大意:
要求找出一个1~n排列的排列,通过
bi=(∏ij=1ai)%n
构成一个新的0~n-1的排列。
题目分析:
- 首先我们必须让n位于最后,否则会过早的导致前缀积能够整除n,导致一定不能得到合法的排列,因为合数一定可以分解为p*q,所以会导致pq|(n-1)!,4和1是特例需要特判。
- 然后我们考虑 (i+1)⋅inv[i] ,因为$(i+1) \cdot inv[i] == (j+1) \cdot inv[j] , 当且仅当i == j ¥
- 证明如下:
(i+1)⋅inv[i]=(j+1)⋅inv[j]⇒i⋅j⋅(i+1)⋅inv[i]=i⋅j⋅(j+1)⋅inv[j]⇒j⋅(i+1)=i⋅(j+1)⇒i⋅j+j=i⋅j+i⇒i=j
- 那么我们知道利用(i+1)*inv[i]能够构成一个排列。
- 那么这个排列的前缀积就是展开如下:
a1⋅a2⋯ai=1⋅inv[1]⋅2⋯inv[i−2]⋅(i−1)⋅inv[i−1]⋅i=i(错相相乘,根据逆元性质相消)
所以得到的依旧是一个排列符合题目要求的性质。
AC代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAX 100007
using namespace std;
typedef long long LL;
LL inv[MAX];
int n;
int main ( )
{
while ( ~scanf ( "%d" , &n ) )
{
if ( n == 1 )
{
puts ( "YES");
puts ( "1" );
}
else if ( n == 4 )
{
puts ( "YES" );
puts ( "1\n3\n2\n4" );
}
else
{
bool flag = true;
for ( int i = 2 ; i*i <= n ; i++ )
if ( n%i == 0 ) flag = false;
if ( flag )
{
puts ( "YES" );
puts ( "1" );
inv[1] = 1;
for ( int i = 2 ; i < n ; i++ )
{
inv[i] = (LL)(n-n/i)*inv[n%i]%n;
printf ( "%I64d\n" , (LL)(i)*inv[i-1]%n );
}
printf ( "%d\n" , n );
}
else puts ( "NO" );
}
}
}