[AGC060D] Same Descent Set

题解

考虑给定一个由 <> 组成的长度为 \(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;
}
posted @ 2022-12-27 20:53  AK_DREAM  阅读(214)  评论(1编辑  收藏  举报