一个朴素的想法是容斥:考虑钦定 \(S\) 集合的位置没有被车覆盖,则答案是 \((-1)^{|S|}2^{c}\),其中 \(c\) 是可以放车的位置,可以直接 dp 做到 \(\mathrm{O}(2^n \text{poly}(n))\),但是难以优化。
延续容斥的想法,注意到钦定一个位置后会直接 ban 掉整列,我们设 \(f(S)\) 表示所有钦定的位置所在列的集合恰好为 \(S\) 的答案,则答案为 \(\sum f(S)\)。考虑二次容斥,钦定 \(T \subseteq S\) 所在列并没有实际钦定位置,则 \(f(S) = \sum (-1)^{|T|}g(T)\)。
如图,把棋盘拆成若干极长段,注意到每段的贡献可以分开计算:假设当前段的长度为 \(len\),有 \(p\) 个位置在 \(S\) 中,有 \(q\) 个位置同时在 \(T\) 中,则贡献为:
- 当前极长段没有钦定位置,贡献为 \(2 ^ {len - p}\)。
- 当前极长段钦定了至少一个位置,贡献为 \(\sum\limits_{i=1}^{p-q}(-1)^i\binom{p-q}{i} = -[p \neq q]\)。
假设当前段高为 \(h\),则总贡献是 \((2^{len-p}-[p \neq q])^h\)。
考虑对极长段建类似笛卡尔树状物,但是这里是多叉的。在决策到 \(u\) 时,子树所在列是否加入 \(S\) 和加入 \(T\) 都已决定了,只需考虑当前高度的列的决策即可。设 \(f(u, i, 0/1)\) 表示子树内有 \(i\) 列在 \(S\) 中,当前 \(p\) 是否等于 \(q\),转移是做树形背包,最后在加入当前连续段的贡献即可。时间复杂度 \(\mathrm{O}(n^2)\)。
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
using ll = long long;
const int N = 410;
const int MOD = 998244353;
int n, h[N], len[N], more[N], a[N];
ll f[N][N][2], g[N][2], b[N];
vector<int> G[N];
void Add(ll &x, ll y) {
x += y; if(x >= MOD) x -= MOD;
}
int Build(int l, int r, int fat) {
if(l == r) return len[l] = more[l] = 1, a[l] = h[l] - h[fat], l;
int u = min_element(h + l, h + r + 1) - h;
len[u] = r - l + 1, a[u] = h[u] - h[fat];
int lst = l - 1;
for(int i = l; i <= r; ++i) if(h[i] == h[u]) {
if(lst != i - 1) G[u].push_back(Build(lst + 1, i - 1, u));
lst = i, ++more[u];
}
if(lst != r) G[u].push_back(Build(lst + 1, r, u));
return u;
}
ll Pow(ll a, int p = MOD - 2) {
ll ans = 1;
for(; p; p >>= 1, a = a * a % MOD)
if(p & 1) ans = ans * a % MOD;
return ans;
}
void Dfs(int u) {
f[u][0][0] = 1;
for(int i = 1; i <= more[u]; ++i) {
for(int p = 0; p <= i; ++p) g[p][0] = g[p][1] = 0;
for(int p = 0; p < i; ++p)
for(int j : {0, 1}) if(f[u][p][j]) {
Add(g[p][j], f[u][p][j]);
Add(g[p + 1][1], f[u][p][j]);
Add(g[p + 1][j], MOD - f[u][p][j]);
}
for(int p = 0; p <= i; ++p)
f[u][p][0] = g[p][0], f[u][p][1] = g[p][1];
}
int now = more[u];
for(int v : G[u]) {
Dfs(v);
for(int p = 0; p <= now + len[v]; ++p) g[p][0] = g[p][1] = 0;
for(int p = 0; p <= now; ++p)
for(int q = 0; q <= len[v]; ++q)
for(int i : {0, 1}) for(int j : {0, 1})
Add(g[p + q][i | j], f[u][p][i] * f[v][q][j] % MOD);
now += len[v];
for(int p = 0; p <= now; ++p)
f[u][p][0] = g[p][0], f[u][p][1] = g[p][1];
}
for(int p = 0; p <= len[u]; ++p)
for(int i : {0, 1}) if(f[u][p][i]) {
ll val = Pow(b[len[u] - p] + MOD - i, a[u]);
f[u][p][i] = f[u][p][i] * val % MOD;
}
}
int main() {
//freopen("data.in", "r", stdin);
//freopen("code.out", "w", stdout);
cin.tie(0)->sync_with_stdio(0);
cin >> n;
for(int i = 1; i <= n; ++i) cin >> h[i];
b[0] = 1;
for(int i = 1; i <= n; ++i) b[i] = b[i - 1] * 2 % MOD;
int rt = Build(1, n, 0);
Dfs(rt);
ll ans = 0;
for(int p = 0; p <= len[rt]; ++p)
for(int i : {0, 1}) if(f[rt][p][i])
Add(ans, f[rt][p][i]);
printf("%lld\n", ans);
return 0;
}
标签:Rooks,int,AGC041F,钦定,位置,len,Histogram,include,MOD
From: https://www.cnblogs.com/Aria-Math/p/18660412