题解:P11563 【MX-X7-T4】[LSOT-3] 命运
fish_love_cat · · 题解
口胡一下。假了就删题解跑路。
把原题的序列看成给定每个点的父亲,于是得到基环树。
然后题目中排列的确定方式,看起来就像,要么继承基环树的原始边,要么给边反个向抄下来。
看眼样例,我草怎么假了。
我草怎么还有二元环,这么恐怖。不是很会,先放着等会来想。
如果是自环怎么办。分讨一下发现这个边好像动不了。
因为我们变形后的图要求是若干个环,因此如果自环下面有挂点那么就炸完了。
考虑三元环以及更多元的情况。
显然的这个时候依旧是边反向,于是这个环也是钉死的,所以环下面挂点一样是死路一条。
于是好像特例就只有二元环。
其他环如果合法,产生的贡献也只有顺时针一遍环以及逆时针一遍环。自环要扔掉。
考虑这个时候咋做,我们得到了一个基二元环树森林。
考察原始定义,我们需要保证对于每个原先存在的边,新图中存在其反向边或者原边。
所以二元环中的一个边可以直接被挪出来,于是变成多个树的问题。
然后容易发现如果树不是一条链的话加边是不会成环的。本质问题是环不会出现度数超过预期
那么我们有了许多链。
现在我们可以把这些链拼成若干个环。然后你发现这等价于排列数。
于是应该差不多是做完了。每个环还可以反个向啥的,反正就是个很普通的数数。
注意二元环独立成环的时候正反都一样,此时不应该乘这个
预处理信息可以用并查集。
#include<bits/stdc++.h>
#define N 1000000
#define mod 998244353
#define int long long
using namespace std;
int qpow(int a,int b=mod-2){
int ans=1;
while(b){
if(b&1)ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
map<int,bool>mp[N+5];
int fa[N+5];
bool flg[N+5];
bool ban[N+5];
int siz[N+5];
int A[N+5],inv[N+5];
int find(int x){
return x==fa[x]?x:fa[x]=find(fa[x]);
}
int C(int n,int m){
return A[n]*inv[m]%mod*inv[n-m]%mod;
}
signed main(){
int n;
cin>>n;
A[0]=1;
for(int i=1;i<=N;i++)
A[i]=A[i-1]*i%mod;
inv[N]=qpow(A[N]);
for(int i=N;i;i--)
inv[i-1]=inv[i]*i%mod;
for(int i=1;i<=n;i++)
fa[i]=i,siz[i]=1;
for(int i=1;i<=n;i++){
int x;
cin>>x;
if(x==i){
ban[i]=1;
continue;
}
if(!mp[x][i]){
if(find(x)==find(i))
flg[find(x)]=1;
else
flg[find(i)]|=flg[find(x)],
siz[find(i)]+=siz[find(x)],
fa[find(x)]=find(i);
}
mp[x][i]=1;
mp[i][x]=1;
}
int ret2=1;
int sum1=0,sum2=0;
for(int i=1;i<=n;i++){
if(mp[i].size()>2){
cout<<"0\n";
return 0;
}
if(ban[i]&&siz[find(i)]!=1){
cout<<"0\n";
return 0;
}
if(ban[i])continue;
if(i!=fa[i])continue;
if(siz[i]==1)continue;
if(flg[i]){
ret2=ret2*2%mod;
continue;
}
sum1++;
if(siz[i]==2)sum2++;
}
int op=1;
int ret=0;
for(int i=0;i<=sum2;i++){
int flc=C(sum2,i)*qpow(2,sum1-i)%mod*A[sum1-i]%mod;
ret+=flc*op;
ret%=mod;
ret+=mod;
ret%=mod;
op=-op;
}
cout<<ret*ret2%mod;
return 0;
}
//「呃,不是你想的那样啦。」
//「我明白,我全都明白,好吗?」
// 缇亚忒温柔地重复道。她绝对什么都不明白。