[AGC060D] Same Descent Set
题解
考虑给定一个由 <
和 >
组成的长度为 的字符串,第 位为 <
表示 ,否则表示 。
假设有一个这样的字符串 ,那么设 表示满足 限制的排列的数量,那么题目所求即为 。
考虑如何计算 。假设字符串 中 这个集合的位置是 >
,对这些位置是否满足限制进行容斥,就可以转化为以下问题:
设 为满足 的排列 的数量 ,那么枚举字符串 中为 >
的集合 ,有 ,答案即为 。
先来看看 怎么求。将一个长为 的序列在所有 的位置断开,假设断成了 段,长度为 ,那么方案数就是 。(式1)
改变求和顺序,考虑对于一对 有多少可以选的 ,答案为 。
比较难处理的就是这个 的系数,注意到 恰好有 个子集,问题转化为求:
。(式2)
那么注意到在所有 的位置一定是断开了的,上面我们计算 的式子告诉我们断开的两段之间的贡献是独立的,这启发我们使用一个 dp 来逐位确定 以及此时的贡献。
设 表示当前考虑了前 位,并且 中的最后一个元素恰为 时的答案。那么 。除以 是 (式2) 中那个 的贡献。
(式2) 中的 可以任意包含 这一段中的元素,为此我们需要计算出 表示任意划分长为 的段的贡献。 同样可以通过递推来得到: ,这样就计算到了 (式1) 中分母的贡献以及 (式2) 中 的贡献。
合理设置 和 的边界值,最后 即为所求。当然这还不是最终答案,需要将之前没有乘上的 给乘上。
的计算显然可以通过多项式求逆或者直接分治NTT求出。求出 后 也可同理求出。
时间复杂度 或 。
代码
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现