题目链接:B-Strange_Permutations_第 46 届 ICPC 国际大学生程序设计竞赛亚洲区域赛(上海) (nowcoder.com)
Q i + 1 ≠ P Q i Q_{i+1}\ne P_{Q_i} Qi+1=PQi
从题意我们可以得出对于 Q Q Q排列的元素,元素” i i i‘’后面不能是‘’ P i P_i Pi‘’
然后就不知道怎么办了qwq,发现不了其它性质。而且和之前做的不太一样。它必须要恰好构成一条排列“链”的形式,不能走着走着变成环。无法想到咋求方案数,咋多项式做。。。
考虑把排列 Q Q Q看作一个边集 ( Q 1 , Q 2 ) , ( Q 2 , Q 3 ) , . . . , ( Q n − 1 , Q n ) (Q_1,Q_2),(Q_2,Q_3),...,(Q_{n-1},Q_n) (Q1,Q2),(Q2,Q3),...,(Qn−1,Qn)
而排列 P P P相当于图中不存在边集 ( 1 , P 1 ) , ( 2 , P 2 ) . . . ( n , P n ) (1,P_1),(2,P_2)...(n,P_n) (1,P1),(2,P2)...(n,Pn)
也就是我们要对于 1 1 1~ n n n的完全图并去掉了 ( 1 , P 1 ) , ( 2 , P 2 ) , . . . ( n , P n ) (1,P_1),(2,P_2),...(n,P_n) (1,P1),(2,P2),...(n,Pn)边集后的图,找到一条曼哈顿路径,代表 Q Q Q的一种排列。求这样的曼哈顿路径数量
考虑如何求数量。可以容斥
如果没有去掉边的话,即固定选了0条边,有 n ! n! n!种选法。 ( n 0 ) × n ! \binom{n}{0}\times n! (0n)×n!
固定选了1条边,相当于捆绑法,两个点贴在一起,有 ( n − 1 ) ! (n-1)! (n−1)!种选法。 ( n 1 ) × ( n − 1 ) ! \binom{n}{1}\times (n-1)! (1n)×(n−1)!
固定选了2条边,。。不对劲了。要保证选的边是曼哈顿路径上的边才行。。。
寄了。
我们还需要考虑“容斥固定选的边”是否可行的情况。显然 ( 2 , 2 ) (2,2) (2,2)这种边集以及 ( 1 , 2 ) , ( 2 , 1 ) (1,2),(2,1) (1,2),(2,1)这种边集是不可能选到的
由于 P P P是一个排列,相当于若干个环,而曼哈顿路径就是要保证无环的情况。我们可以考虑看每个环中选边的情况!这样就能保证不形成环!
对于 P P P中一个点(边)数为 k k k的环。我们可以选 0 0 0到 k − 1 k-1 k−1条边。因为选 k k k条边就形成环了。每个环都这么选择,显然是互不影响,互相独立的。
对于单独一个环:选了 i i i条边的方案数是 ( k i ) \binom{k}{i} (ik)
我们建成生成函数 f ( x ) = ∑ i = 0 k − 1 ( k i ) x i f(x)=\sum\limits _{i=0}^{k-1}\binom{k}{i}x^i f(x)=i=0∑k−1(ik)xi
多个环的情况相当于 F ( x ) = ∏ f ( x ) F(x)=\prod f(x) F(x)=∏f(x),可以分治NTT解决
对于 [ x i ] [x^i] [xi]的系数,表示已经固定选了 i i i条边的方式。我们排列方案还没考虑清楚。我们每连接一条边,相当于把两个点集合并成一个全新的点集。如没有连边时为 n ! n! n!,连接一条边时为 ( n − 1 ) ! (n-1)! (n−1)!,连接两条边时为 ( n − 2 ) ! (n-2)! (n−2)!……所以已经固定选了 i i i条边,排列方案数为 ( n − i ) ! (n-i)! (n−i)!
答案即为 ∑ i = 0 n ( − 1 ) i ( n − i ) ! [ x i ] F ( x ) \sum\limits _{i=0}^n (-1)^i(n-i)![x^i]F(x) i=0∑n(−1)i(n−i)![xi]F(x)
#include<iostream>
#include<algorithm>
#include<string.h>
#include<vector>
using namespace std;
// #define debug(x) cout<<"[debug]"#x<<"="<<x<<endl
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
const double eps=1e-8;
const int INF=0x3f3f3f3f;
const int N=100005,M=150005;
int p[N];
bool st[N];
typedef vector<ll> Poly;
vector<Poly> f;
const int mod=998244353,G=3,Gi=332748118;
int bit,tot;
int rev[M];
ll qmi(ll a,ll b,ll p)
{
ll res=1;
while(b)
{
if(b&1)
res=res*a%p;
a=a*a%p;
b>>=1;
}
return res;
}
void NTT(Poly &a,int inv)
{
for(int i=0;i<tot;i++)
if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int mid=1;mid<tot;mid*=2)
{
ll w1=qmi(inv==1?G:Gi,(mod-1)/(2*mid),mod);
for(int i=0;i<tot;i+=mid*2)
{
ll wk=1;
for(int j=0;j<mid;j++,wk=wk*w1%mod)
{
ll x=a[i+j];
ll y=wk*a[i+j+mid]%mod;
a[i+j]=(x+y)%mod;
a[i+j+mid]=(x-y+mod)%mod;
}
}
}
if(inv==-1)//就不用后面除了
{
ll intot=qmi(tot,mod-2,mod);
for(int i=0;i<tot;i++)
{
a[i]=a[i]*intot%mod;
}
}
}
Poly mul(Poly a,Poly b)//deg是系数的数量,所以有0~deg-1次项
{
int deg=(int)a.size()+b.size()-1;
bit=0;
while((1<<bit)<deg) bit++;//至少要系数的数量
tot=1<<bit; //系数项为0~tot-1
for(int i=0;i<tot;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
Poly c(tot);
a.resize(tot),b.resize(tot);
NTT(a,1),NTT(b,1);
for(int i=0;i<tot;i++) c[i]=a[i]*b[i]%mod;
NTT(c,-1);
c.resize(deg);
return c;
}
int fact[N],infact[N];
void init(int n)
{
fact[0]=1;
for(int i=1;i<=n;i++)
{
fact[i]=1ll*fact[i-1]*i%mod;
}
infact[n]=qmi(fact[n],mod-2,mod);
for(int i=n-1;i>=0;i--)
{
infact[i]=1ll*infact[i+1]*(i+1)%mod;
}
}
int C(int n,int m)
{
// if(n<m) return 0;
return 1ll*fact[n]*infact[m]%mod*infact[n-m]%mod;
}
Poly cdq_ntt(int l,int r)
{
if(l==r) return f[l];
int mid=l+r>>1;
return mul(cdq_ntt(l,mid),cdq_ntt(mid+1,r));
}
int main()
{
init(N-1);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&p[i]);
vector<int> h;//统计每个环的点(边)数
for(int i=1;i<=n;i++)
{
if(st[i]) continue;
int jishu=1;
st[i]=true;
int now=p[i];
while(now!=i)
{
jishu++;
st[now]=true;
now=p[now];
}
h.push_back(jishu);
}
f.resize(h.size());
for(int i=0;i<f.size();i++)
{
f[i].resize(h[i]);
for(int j=0;j<h[i];j++)
{
f[i][j]=C(h[i],j);
}
}
Poly F=cdq_ntt(0,f.size()-1);
ll res=0;
for(int i=0;i<F.size();i++)
{
ll ans=fact[n-i]*F[i]%mod;
if(i%2) res=(res-ans+mod)%mod;
else res=(res+ans)%mod;
}
printf("%lld\n",res);
}