CF960G Bandit Blues
半个月前做的题,这段时间一直在颓所以没写题解,今天突然想起来才准备补上。
考虑枚举最大值 的位置 ,那么排列就被分成 个段 和 ,而且 , 不可能是前缀最大值;, 不可能是后缀最大值。
于是两个段可以互相独立地进行计数。就是说 中有 个前缀最大值, 中有 个后缀最大值。
令 表示 个数的排列前缀最大值有 个的方案数。枚举 的位置 ,从剩下 个数中选 个放到 ,那么答案就是:
考虑 怎么求,我们从大到小往当前排列 中插入最小值 ,考虑 个数的排列有 个位置可以插入:
- 若 插入到 的开头,新增一个前缀最大值,则 。
- 若 插入到 的其它位置,前缀最大值个数不变,则 。
于是:
看得出来 这东西就是第一类斯特林数 。
那么答案就是:
至此,不怕麻烦的同学就可以直接计算 两列的斯特林数,然后卷一下就行了。
如果继续化简,有 种方式:
- 代数推导(天地灭)
- 生成函数(天地灭灭灭灭灭灭灭灭)
- 组合意义(非常的新鲜,非常的美味啊!)
所以我们考虑组合意义。
求和里面那坨就相当于从 个数中选 个生成 个圆排列,剩下 个数生成 个圆排列的方案数。
按照范德蒙德卷积的思路,相当于 个数中先生成 个圆排列,再从这么多圆排列中选出 个的方案数。由于选出来后 个圆排列和 ( 个圆排列总大小)是对应的,所以既不会重也不会漏。
于是答案就是 。
考虑到第一类斯特林数没有实用的通项公式,直接大力把第 行算出来就行了。复杂度 。
typedef vector<int> poly;
const int G = 114514;
const int P = 998244353;
const int N = 6e5 + 600;
int n, a, b, fac[N], ifac[N];
poly bs;
int qpow(int p, int q) {
int res = 1;
for (; q; q >>= 1, p = 1ll * p * p % P)
if (q & 1) res = 1ll * res * p % P;
return res;
}
const int iG = qpow(G, P - 2);
void init(int lim) {
fac[0] = 1;
for (int i = 1; i <= lim; i++)
fac[i] = 1ll * fac[i - 1] * i % P;
ifac[lim] = qpow(fac[lim], P - 2);
for (int i = lim - 1; ~i; i--)
ifac[i] = 1ll * ifac[i + 1] * (i + 1) % P;
}
void NTT(int *f, int len, int lim, int op) {
static int tr[N];
for (int i = 1; i < len; i++)
tr[i] = (tr[i >> 1] >> 1) | ((i & 1) << (lim - 1));
for (int i = 0; i < len; i++)
if (i < tr[i]) swap(f[i], f[tr[i]]);
for (int o = 2, k = 1; k < len; o <<= 1, k <<= 1) {
int tg = qpow(~op ? G : iG, (P - 1) / o);
for (int i = 0; i < len; i += o) {
for (int j = 0, w = 1; j < k; j++, w = 1ll * w * tg % P) {
int x = f[i + j], y = 1ll * w * f[i + j + k] % P;
f[i + j] = (x + y) % P, f[i + j + k] = (x - y + P) % P;
}
}
}
if (~op) return;
int iv = qpow(len, P - 2);
for (int i = 0; i < len; i++)
f[i] = 1ll * f[i] * iv % P;
}
poly Mul(poly x, poly y) {
static int tx[N], ty[N];
int len = 1, lim = 0, sx = x.size(), sy = y.size();
while (len < (sx + sy)) len <<= 1, lim++;
memset(tx, 0, sizeof(int) * (len + 10));
memset(ty, 0, sizeof(int) * (len + 10));
for (int i = 0; i < sx; i++) tx[i] = x[i];
for (int i = 0; i < sy; i++) ty[i] = y[i];
NTT(tx, len, lim, 1), NTT(ty, len, lim, 1);
for (int i = 0; i < len; i++)
tx[i] = 1ll * tx[i] * ty[i] % P;
NTT(tx, len, lim, -1);
poly res;
for (int i = 0; i < sx + sy - 1; i++) res.pb(tx[i]);
return res;
}
poly conq(int len) {
if (len == 1) return bs;
if (len & 1) {
poly tp = conq(len - 1), res;
res.pb(1ll * tp[0] * (len - 1) % P);
for (int i = 1; i < len; i++)
res.pb((tp[i - 1] + 1ll * tp[i] * (len - 1) % P) % P);
res.pb(tp[len - 1]);
return res;
}
poly tp = conq(len >> 1), ta, tb, tc;
int res = 1;
for (int i = 0; i <= (len >> 1); i++)
ta.pb(1ll * tp[i] * fac[i] % P), tb.pb(1ll * res * ifac[i] % P), res = 1ll * res * (len >> 1) % P;
reverse(ta.begin(), ta.end());
ta = Mul(ta, tb);
for (int i = 0; i <= (len >> 1); i++)
tc.pb(1ll * ifac[i] * ta[(len >> 1) - i] % P);
return Mul(tp, tc);
}
int C(int n, int m) {
if (n < m || m < 0) return 0;
return 1ll * fac[n] * ifac[m] % P * ifac[n - m] % P;
}
int main() {
n = rd(), a = rd(), b= rd(), init(n << 1), bs = poly(2, 1), bs[0] = 0;
if (n == 1) {
if (a == 1 && b == 1) puts("1");
else puts("0");
return 0;
}
if (a + b - 2 > n) return puts("0"), 0;
poly ans = conq(n - 1);
wr(1ll * ans[a + b - 2] * C(a + b - 2, a - 1) % P);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话