[cf698F]Coprime Permutation

P[1,n]中所有素数构成的集合,g(n)n所有素因子乘积(特别的,g(1)=1

对于一个合法排列{qi},注意到xP{1},qx两两互素,也即每一个素因子至多只能在其中出现一次,分析数量不难得到每一个数至多只能包含一个素因子(注意仅有1不包含素因子)

换言之,xP{1},g(qx)恰为P{1}的一个排列

不妨假设已经确定这些g(qx),那么有g(qi)=xP{1},gcd(x,i)1g(qx)(其中i1,注意到包含g(qx)这个素因子当且仅当gcd(x,i)1,同时这样考虑了P中所有素数,即正确)

进一步的,对于g(x)=g(y)xy在是否互素的判定中是等价的,因此此时已经可以确定{qi}合法,并且仅需保证数量相同(即x,g(i)=x1=g(qi)=x1),其内部是可以任意排列的

此时,问题即变为怎样的P{1}的排列(填入xP{1},g(qx))能保证"数量相同"

(下面较详细地分析了细节,如果感性理解可以直接跳过看结论)

对"数量相同"的式子容斥,前者即为
g(i)=x1={0μ(x)=0S0SP(1)|S||S0|(pSp)g(i)1μ(x)0
S0x素因子集合,另外注意μ(pSp)0

类似地,后者g(qi)=x1只需要将式子中的g(i)均改为g(qi)即可

由此,"数量相同"的充分条件即μ(x)0,xg(i)1=xg(qi)1,同时显然这也是必要的

关于前者,由于μ(x)0,那么xg(i)即等价于xi,前者也即为nx

关于后者,再令S0={ppP{1}qp1,g(qp)x},那么xg(qi)(其中i1)根据g(qi)的式子即等价于pS0,gcd(p,i)1,又因为pS0P{1},gcd(p,i)1也即p1pi

换言之,后者也即为{n(x=1)[xg(q1)]+{0q11,g(q1)xnpS0potherwise(x1)

对这三类分别讨论(注意需要同时满足),具体如下——

1.x=1时必然成立(与g(qx)无关)

2.x1q11,g(q1)x时,取x=g(q1)即有ng(q1)=1,推出g(q1)>n2,同时此时q1必然恰为g(q1)(而不能是g(q1)α),同时此时x必然为g(q1),也即成立(另外注意q1还可以等于1)

3.x1otherwise,取xq1的素数,令p满足pP{1}g(qp)=x,即有nx=np,并且xn时此式成立必然要有p=x,证明如下:

xp>n,则np<xnnx,即不相等

xpn,不妨假设x<p,则nx=p+nxpx>x+nxpp

而在此条件下,考虑x即是要求nx=npS0p,此时将x的素因子和p对应,其中仅有>n的项不相同,这至多存在一项且被n除后也相同,再除以相同的数显然仍相同

综上,"数量相同"当且仅当满足以下条件:

1.q1=1q1Pq1>n2

2.xP{1}xq1,有nx=np(其中pP{1}g(qp)=x

这样填完这个排列后,若q1=1即已经填完,q11即还剩下一个位置,该位置(下标)为>n2的素数,并且即要填剩下的1,因此实际上即将n1看作1就可以归为一种情况

h(x)={1(x=1)nx(xP),那么"数量相同"当且仅当xP{1},h(x)=h(g(qx)),换言之可以看作h(x)相同的数内部可以任意排列

接下来,考虑若干个给定的qi,处理过程如下——

1.iqi不超过n的素因子(存在性)应该相同,且必须都存在/不存在>n的素因子(显然至多一个)

2.(若iqi都存在>n的素因子)令p1,p2分别为iqi大于n的素因子,则需要满足np1=np2,并且在xP{1}的排列中强制g(qp1)=p2,记录这样的关系(防止重复)并将h(p2)所在组去掉一个

n的素因子不需要减小,因为其本身必须对应相同)

3.对于最后g(qi)确定后的排列中,g(qi)所在的组减少一个数

最终,将两者排列(求阶乘)相乘即可

时间复杂度为o(n),可以通过

复制代码
 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define N 1000005
 4 #define mod 1000000007
 5 #define ll long long
 6 int n,ans,a[N],fac[N],vis[N],p[N],mx[N],Mn[N],f[N],r0[N],r1[N],cnt[N],cnt0[N];
 7 int main(){
 8     fac[0]=mx[1]=Mn[1]=1;
 9     for(int i=1;i<N;i++)fac[i]=(ll)fac[i-1]*i%mod;
10     for(int i=2;i<N;i++){
11         if (!vis[i]){
12             p[++p[0]]=i;
13             mx[i]=Mn[i]=i;
14         }
15         for(int j=1;(j<=p[0])&&(i*p[j]<N);j++){
16             vis[i*p[j]]=1;
17             mx[i*p[j]]=mx[i];
18             if (i%p[j])Mn[i*p[j]]=Mn[i]*p[j];
19             else{
20                 Mn[i*p[j]]=Mn[i];
21                 break;
22             }
23         }
24     }
25     scanf("%d",&n);
26     for(int i=1;i<=n;i++)scanf("%d",&a[i]);
27     for(int i=1;i<=n;i++)cnt[Mn[i]]++;
28     f[1]=1;
29     for(int i=1,j;(i<=p[0])&&(p[i]<=n);i=j){
30         j=i+1,f[p[i]]=p[i];
31         while ((j<=p[0])&&(p[j]<=n)&&(n/p[i]==n/p[j]))f[p[j++]]=p[i];
32         if ((j>p[0])||(p[j]>n)){
33             for(int k=i;k<j;k++)f[p[k]]=1;
34         }
35     }
36     cnt0[1]++;
37     for(int i=1;(i<=p[0])&&(p[i]<=n);i++)cnt0[f[p[i]]]++;
38     for(int i=1;i<=n;i++)
39         if (a[i]){
40             int x=i,y=a[i];
41             cnt[Mn[y]]--;
42             if ((f[mx[x]]!=f[mx[y]])||(Mn[x]/mx[x]!=Mn[y]/mx[y])){
43                 printf("0\n");
44                 return 0;
45             }
46             if ((x==1)||((ll)mx[x]*mx[x]>n)){
47                 if ((!r0[mx[x]])&&(!r1[mx[y]])){
48                     cnt0[f[mx[x]]]--;
49                     r0[mx[x]]=mx[y],r1[mx[y]]=mx[x];
50                 }
51                 if ((r0[mx[x]]!=mx[y])||(r1[mx[y]]!=mx[x])){
52                     printf("0\n");
53                     return 0;
54                 }
55             }
56         }
57     ans=1;
58     for(int i=1;i<=n;i++)ans=(ll)ans*fac[cnt[i]]%mod*fac[cnt0[i]]%mod;
59     printf("%d\n",ans);
60     return 0;
61 }
View Code
复制代码

 

posted @   PYWBKTDA  阅读(320)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
历史上的今天:
2020-10-19 [cf1421E]Swedish Heroes
点击右上角即可分享
微信分享提示