前言
update 2021.6.17
-
给一个数竞大佬做了这道题,他想了很久,但是这是他们书上的原题。。。
-
更新了解法并优化了博客内容。
倒在最后的构造。
题目
讲解
判断是否有解
首先根据暴力我们不难看出只有当 (nle 4) 或者 (n) 为质数的时候才有解。
当然我们也可以稍微证明一下。
当 (n>4) 时,如果 (n) 是合数,则一定存在 (a e b,a,b<n,n|ab) ,那么此后的前缀积为 (0) 了,直接GG,下面是对 (a,b) 的存在性的证明:
- 如果(n)不是一个质数的完全平方数,则存在 (ab=n)。
- 如果(n)是一个质数的完全平方数,令 (p^2=n) ,显然 (p>2) ,则存在 (a=p,b=2p) ,那么 (n|ab)。
构造求解
我们可以看出两个性质:
(1) 一定放在首位,放在中间的话一定会使得前一位的前缀积与当前的前缀积相等。
(n) 一定放在末位,如果放在前面的话会导致后面的前缀积都为 (0)。
因为 (n) 为质数,下文的 (n) 都使用 (p) 代替。
考虑根据原根 (g) 具有的性质求解。
简单提一下原根的性质,对于 (∀i,jin[0,n-2],i e j),不存在 (g^i≡g^j pmod p)。
我们只需将 (g^0,g^1,...,g^{n-2}) 输出即可。
吗?
然而这是前缀积,不能直接输出,根据(g^{p-1}≡1 pmod p),我们有一个巧妙的构造方式。
并不是我想出来的。
我们只需按照这样的顺序排列即可:
(g^0,g^{p-2},g^{2},g^{p-4},g^{4},...)。
如果将它们转化为前缀积的形式,再将指数对(p-1)取模那么可以化为:
(g^0,g^{p-2},g^{1},g^{p-3},g^{2})。
正确性不难证明,原根直接暴力找就好了。
注意(nle4)的时候要特判。
代码
冗长
bool vis[MAXN];
int prime[MAXN],pn,ys[MAXN],tot;
void sieve(int x)
{
for(int i = 2;i <= x;++ i)
{
if(!vis[i]) prime[++pn] = i;
for(int j = 1;j <= pn && i * prime[j] <= x;++ j)
{
vis[i * prime[j]] = 1;
if(i % prime[j] == 0) break;
}
}
}
int qpow(int x,int y,int MOD)
{
int ret = 1;
while(y){if(y & 1) ret = 1ll * ret * x % MOD;x = 1ll * x * x % MOD;y >>= 1;}
return ret;
}
void solve3()
{
//注意要特判
if(n == 1) {printf("YES
1");return;}
if(n == 2) {printf("YES
1
2");return;}
if(n == 3) {printf("YES
1
2
3");return;}
if(n == 4) {printf("YES
1
3
2
4");return;}
//想明白题目之前打的筛质数的板子,以为会用上,结果用处不大
sieve(n);
//大于4的合数无解
if(vis[n]) {printf("NO");return;}
//开始寻找原根
int phi = n-1,G = 0;
for(int i = 1;i <= pn && prime[i] <= phi;++ i)
if(phi % prime[i] == 0)
ys[++tot] = prime[i];
for(int i = 2;i < phi && !G;++ i)
{
bool g = 1;
for(int j = 1;j <= tot && g;++ j)
if(qpow(i,phi/ys[j],n) == 1) g = 0;
if(g) G = i;
}
//构造求解
printf("YES
");
for(int i = 0;i < (n-1)/2;++ i) Put(qpow(G,2*i,n),'
'),Put(qpow(G,n-(2*(i+1)),n),'
');
Put(n);
}
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n = Read();
solve3();
return 0;
}
后记
其实还有一种更简单的做法:(1,frac{2}{1},frac{3}{2},frac{4}{3},...,frac{n-1}{n-2},n),正确性显然。