ICPC2021 上海 Strange Permutations 和 ICPC2021 沈阳 Perfect Matchings
Strange Permutations
Given a permutation \(P\) of \(\{1,2,\cdots, n\}\), determine the number of \(\{1,2,\cdots, n\}\) permutations \(Q\) satisfying that \(\forall i \in \{1, 2, \cdots, n - 1\}, Q_{i+1} \neq P_{Q_i}\). Output the number modulo \(998244353\).
\(1\le n\le 10^5\)
题解
首先这个\(Q_{i+1} \neq P_{Q_i}\)限制很奇怪,不好理解。不如把\(P\)看成一个函数,然后就题目变成了有\(n\)个限制,排列中不能连续地出现\(x, P(x)\)。
容斥是显然的。钦定一定违反了\(k\)个限制,然后有一些数前后黏在一起,带上容斥系数\((-1)^k\)。
但是我们可以发现这些限制形成了一些环,容斥的时候一个环内的限制不可能同时违反。若环长为\(l\),一个排列最多违反\(l-1\)个限制。并且环与环之间是互不影响的。
那么对每个环构造容斥系数多项式,乘起来即可。
时间复杂度\(O(n\log^2 n)\)。
constexpr int L=17, N=1<<L;
using Poly=vector<int>;
int omg[2][N+1], rev[N+1];
int fac[N+1], inv[N+1], ifac[N+1];
void initNTT(){
omg[0][0]=1, omg[0][1]=fastPow(3, (MOD-1)/N);
omg[1][0]=1, omg[1][1]=fastPow(omg[0][1], MOD-2);
rev[0]=0, rev[1]=1<<(L-1);
fac[0]=fac[1]=1;
inv[0]=inv[1]=1;
ifac[0]=ifac[1]=1;
for(int i=2; i<=N; ++i){
omg[0][i]=mul(omg[0][i-1], omg[0][1]);
omg[1][i]=mul(omg[1][i-1], omg[1][1]);
rev[i]=rev[i>>1]>>1|(i&1)<<(L-1);
fac[i]=mul(fac[i-1], i);
inv[i]=mul(MOD-MOD/i, inv[MOD%i]);
ifac[i]=mul(ifac[i-1], inv[i]);
}
}
void NTT(Poly &a, int dir){
int lim=a.size(), len=log2(lim);
for(int i=0; i<lim; ++i){
int r=rev[i]>>(L-len);
if(i<r) swap(a[i], a[r]);
}
for(int i=1; i<lim; i<<=1)
for(int j=0; j<lim; j+=i<<1)for(int k=0; k<i; ++k){
int t=mul(omg[dir][N/(i<<1)*k], a[j+i+k]);
a[j+i+k]=add(a[j+k], MOD-t), a[j+k]=add(a[j+k], t);
}
if(dir)for(int i=0; i<lim; ++i)
a[i]=mul(a[i], inv[lim]);
}
Poly operator*(Poly a, Poly b){
int n=a.size()+b.size()-1, lim=1<<(int)ceil(log2(n));
a.resize(lim), NTT(a, 0);
b.resize(lim), NTT(b, 0);
for(int i=0; i<lim; ++i) a[i]=mul(a[i], b[i]);
NTT(a, 1), a.resize(n);
return a;
}
int p[N], vis[N];
Poly f[N];
int C(int n, int m){
return mul(fac[n], mul(ifac[m], ifac[n-m]));
}
Poly build(int l, int r){
if(l==r) return f[l];
int mid=(l+r)>>1;
return build(l, mid)*build(mid+1, r);
}
int main(){
initNTT();
int n=read<int>();
for(int i=1; i<=n; ++i) read(p[i]);
int m=0;
for(int i=1; i<=n; ++i)if(!vis[i]){
int len=0;
function<void(int)> dfs=[&](int x)->void{
vis[x]=1, ++len;
if(!vis[p[x]]) dfs(p[x]);
};
dfs(i);
f[++m].resize(len+1);
for(int j=0; j<=len-1; ++j){
f[m][len-j]=C(len, j);
if(j&1) f[m][len-j]=MOD-f[m][len-j];
}
}
// for(int i=1; i<=m; ++i){
// cerr<<"f"<<i<<" = ";
// for(int a:f[i]) cerr<<a<<" ";
// cerr<<endl;
// }
Poly g=build(1, m);
int ans=0;
for(int i=1; i<=n; ++i) ans=add(ans, mul(g[i], fac[i]));
printf("%d\n", ans);
return 0;
}
Perfect Matchings
AAA gets a complete graph of \(2n\) vertices, where every pair of distinct vertices is connected by a unique edge, as a birthday present. However, AAA thinks the complete graph is not that beautiful and he decides to delete \(2n-1\) edges that form a tree.
Now he wonders the number of different perfect matchings in the remaining graph. Note that a perfect matching is a set of \(n\) edges where no two edges share a common vertex. Since the answer may be very large, you only need to output the answer modulo \(998\,244\,353\).
\(2\le n\le 2\,000\)
题解
不删边的时候答案是\((2n-1)!!\)。
容斥选了哪些删了的树边即可。需要用到树上背包DP及其优化。
时间复杂度\(O(n^2)\)。
constexpr int N=4e3+10;
int fac[N];
vector<int> to[N];
int f[N][N][2], g[N][2], siz[N];
void dfs(int x, int fa){
f[x][0][0]=1, siz[x]=1;
for(int y:to[x])if(y!=fa){
dfs(y, x);
for(int i=0; i<=(siz[x]+siz[y])/2; ++i)
g[i][0]=g[i][1]=0;
for(int i=0; i<=siz[x]/2; ++i)
for(int j=0; j<=siz[y]/2; ++j){
g[i+j][0]=add(g[i+j][0], mul(f[x][i][0], add(f[y][j][0], f[y][j][1])));
g[i+j][1]=add(g[i+j][1], mul(f[x][i][1], add(f[y][j][0], f[y][j][1])));
g[i+j+1][1]=add(g[i+j+1][1], mul(f[x][i][0], f[y][j][0]));
}
siz[x]+=siz[y];
for(int i=0; i<=siz[x]/2; ++i)
f[x][i][0]=g[i][0], f[x][i][1]=g[i][1];
}
}
int main(){
int n=2*read<int>();
fac[1]=1;
for(int i=3; i<n; ++i) fac[i]=mul(fac[i-2], i);
for(int i=1; i<n; ++i){
int u=read<int>(), v=read<int>();
to[u].push_back(v), to[v].push_back(u);
}
dfs(1, 0);
int ans=0;
for(int i=0; i<=n/2; ++i){
// cerr<<i<<" matches way="<<add(f[1][i][0], f[1][i][1])<<endl;
int sum=mul(add(f[1][i][0], f[1][i][1]), 2*i<n?fac[n-2*i-1]:1);
ans=add(ans, i&1? MOD-sum: sum);
}
printf("%d\n", ans);
return 0;
}