题解
考虑给定一个由 <
和 >
组成的长度为 \(n-1\) 的字符串,第 \(i\) 位为 <
表示 \(p_i<p_{i+1}\) ,否则表示 \(p_i>p_{i+1}\) 。
假设有一个这样的字符串 \(t\) ,那么设 \(cnt(t)\) 表示满足 \(t\) 限制的排列的数量,那么题目所求即为 $\sum\limits_{s} cnt(t)^2 $ 。
考虑如何计算 \(cnt(t)\) 。假设字符串 \(t\) 中 \(S\) 这个集合的位置是 >
,对这些位置是否满足限制进行容斥,就可以转化为以下问题:
设 \(f(x)\) 为满足 \(\forall i\notin x,\ p_i<p_{i+1}\) 的排列 \(p\) 的数量 ,那么枚举字符串 \(t\) 中为 >
的集合 \(S\) ,有 \(cnt(t)=\sum\limits_{a\subseteq S} f(a)*(-1)^{|a|}\) ,答案即为 \(\sum\limits_S \sum\limits_{a,b\subseteq S} f(a)*f(b)*(-1)^{|a|+|b|}\) 。
先来看看 \(f(x)\) 怎么求。将一个长为 \(n\) 的序列在所有 \(i\in x\) 的位置断开,假设断成了 \(k\) 段,长度为 \(d_1,d_2,\cdots d_k\) ,那么方案数就是 \(\dfrac{n!}{d_1!d_2!\cdots d_k!}\) 。(式1)
改变求和顺序,考虑对于一对 \(a,b\) 有多少可以选的 \(S\) ,答案为 \(\sum\limits_{a,b} f(a)*f(b)*(-1)^{|a|+|b|}*2^{n-1-|a|-|b|+|a\cap b|}\) 。
比较难处理的就是这个 \(2^{|a\cap b|}\) 的系数,注意到 \((a\cap b)\) 恰好有 \(2^{|a\cap b|}\) 个子集,问题转化为求:
\(\sum\limits_c (\sum\limits_{c\subseteq a} f(a)/(-2)^{|a|})^2\) 。(式2)
那么注意到在所有 \(i\in c\) 的位置一定是断开了的,上面我们计算 \(f(x)\) 的式子告诉我们断开的两段之间的贡献是独立的,这启发我们使用一个 dp 来逐位确定 \(c\) 以及此时的贡献。
设 \(dp[i]\) 表示当前考虑了前 \(i\) 位,并且 \(c\) 中的最后一个元素恰为 \(i\) 时的答案。那么 \(dp[i]=\sum\limits_{j<i}dp[j]*g(i-j)^2/4\) 。除以 \(4\) 是 (式2) 中那个 \(((-2)^{|a|})^2\) 的贡献。
(式2) 中的 \(a\) 可以任意包含 \([j+1,i-1]\) 这一段中的元素,为此我们需要计算出 \(g(x)\) 表示任意划分长为 \(x\) 的段的贡献。\(g(x)\) 同样可以通过递推来得到:\(g(i)=\sum\limits_{j<i}g(j)/(-2)/(i-j)!\) ,这样就计算到了 (式1) 中分母的贡献以及 (式2) 中 \((-2)^{|a|}\) 的贡献。
合理设置 \(dp[0]\) 和 \(g(0)\) 的边界值,最后 \(dp[n]\) 即为所求。当然这还不是最终答案,需要将之前没有乘上的 \((n!)^2*2^{n-1}\) 给乘上。
\(g\) 的计算显然可以通过多项式求逆或者直接分治NTT求出。求出 \(g\) 后 \(dp\) 也可同理求出。
时间复杂度 \(O(n\log n)\) 或 \(O(n\log^2 n)\) 。
代码
#include <bits/stdc++.h>
#define N 800005
using namespace std;
const int mod = 998244353, inv2 = 499122177;
inline int qmod(int x) { return x<mod?x:x-mod; }
inline int fpow(int x, int t) { int r=1;for(;t;t>>=1,x=1ll*x*x%mod)if(t&1)r=1ll*r*x%mod;return r; }
int w[(1<<20)+5], rev[N], lim;
int fac[N], Inv[N], A[N<<2], B[N<<2];
int f[N], g[N], h[N];
void initMath() {
w[0] = 1;
for (int i = 1; i < (1<<20); i<<=1) {
int wn = fpow(3,(mod-1)/(i<<1));
w[i] = 1; for (int k = 1; k < i; k++) w[i+k] = 1ll*w[i+k-1]*wn%mod;
}
fac[0] = Inv[0] = 1;
for (int i = 1; i <= N-5; i++) fac[i] = 1ll*fac[i-1]*i%mod;
Inv[N-5] = fpow(fac[N-5],mod-2);
for (int i = N-6; i; i--) Inv[i] = 1ll*Inv[i+1]*(i+1)%mod;
}
void init(int mx) {
int l = 0; lim = 1;
while (lim < mx) lim <<= 1, ++l;
for (int i = 0; i < lim; i++) rev[i] = (rev[i>>1]>>1)|((i&1)<<(l-1));
}
void NTT(int *F, int tp) {
for (int i = 0; i < lim; i++) if (i < rev[i]) swap(F[i], F[rev[i]]);
for (int i = 1; i < lim; i<<=1) {
for (int j = 0; j < lim; j+=i+i) {
for (int k = 0; k < i; k++) {
int x = F[j+k], y = 1ll*F[i+j+k]*w[i+k]%mod;
F[j+k] = qmod(x+y); F[i+j+k] = qmod(x-y+mod);
}
}
}
if (tp == -1) {
int I = fpow(lim, mod-2);
reverse(F+1, F+lim);
for (int i = 0; i < lim; i++) F[i] = 1ll*F[i]*I%mod;
}
}
void Mul(int p, int q) {
init(p+q); NTT(A, 1); NTT(B, 1);
for (int i = 0; i < lim; i++) A[i] = 1ll*A[i]*B[i]%mod;
NTT(A, -1);
}
void solve(int l, int r, int *_f, int *_g) {
if (l == r) return;
int mid = (l + r) >> 1;
solve(l, mid, _f, _g);
for (int i = l; i <= mid; i++) A[i-l] = _f[i];
for (int i = 1; i <= r-l; i++) B[i-1] = _g[i];
Mul(mid-l+1, r-l);
for (int i = mid+1; i <= r; i++) _f[i] = qmod(_f[i]+A[i-l-1]);
for (int i = 0; i < lim; i++) A[i] = B[i] = 0;
solve(mid+1, r, _f, _g);
}
int main() {
initMath();
int n; scanf("%d", &n);
for (int i = 1; i <= n; i++) h[i] = mod-1ll*Inv[i]%mod*inv2%mod;
g[0] = mod-2; solve(0, n, g, h);
int inv4 = fpow(4,mod-2);
for (int i = 1; i <= n; i++) g[i] = 1ll*g[i]*g[i]%mod*inv4%mod;
f[0] = 4; solve(0, n, f, g);
int ans = 1ll*f[n]*fac[n]%mod*fac[n]%mod*fpow(2,n-1)%mod;
printf("%d\n", ans);
return 0;
}
标签:cnt,Set,Descent,limits,int,sum,cap,AGC060D,dp
From: https://www.cnblogs.com/ak-dream/p/17008987.html