[AGC041F] Histogram Rooks
容斥好题!神Itst!
题意简述:给一个长度为 \(n\) ,高度不均的棋盘,第 i 列高度为 \(h_i\) ,你可以在棋盘上放置车(车可以直着走或横着走,但不能越过棋盘中间的空),称一个位置被控制,当且仅当其通过棋盘上的格子能被至少一个车到达
求有多少放置车的方案,使得每个位置都被控制
\(n \leq 400,h_i \ge 1\)
solution
看到每个位置都被覆盖,考虑容斥
一个朴素的想法是,我们可以钦定若干个位置(以下我们称这种点为关键点)的集合 \(U\) 没有被控制,然后计算在此前提下可以放车的位置 \(p\) ,那么贡献便是 \((-1)^{|U|} * 2 ^ p\)
考虑如何优化这个容斥, 注意到因为列总是连续的,所以如果一个列有一个位置是关键点,那么整个列都不能放车
考虑枚举至少有一个关键点的位置的集合 S
那么对于每个极长行连通块,假设长度为 len ,中间在 S 内的列的数量为 p 我们的贡献就是
- 一个关键点都不放,那么贡献是 \(2 ^ {len - p}\)
- 枚举放了多少关键点,贡献是 \(\sum_{i = 1}^p \binom{p}{i}(-1)^i = -[p != 0]\)
所以总贡献是两者之和
那么我们成功将问题转化为枚举 S ,S 中的一列至少有一个关键点,每一行的贡献是 \(2^{len - p} - [p != q]\),总贡献是每一行贡献的积(注意我们以下考虑的是这个新问题,继续容斥也是基于这个新问题,我们可以把原问题抛掉)
发现 S 的每一列至少有一个关键点不好处理,考虑继续容斥,根据子集反演,设 T \(\subseteq\) S,表示 T 中的元素钦定没有关键点,容斥系数为 \((-1)^{|T|}\)
那么我们继续考虑行极大连通块,我们再设一个变量 q ,表示有 q 列在 T 里面,继续讨论贡献
- 一个关键点不放,贡献仍然是 \(2 ^ {len - p}\)
- 枚举放了多少关键点,贡献是 \(\sum_{i = 1}^{p - q} \binom{p-q}{i}(-1)^i = -[p != q]\)
那么我们知道 S 和 T 就可以算出贡献
枚举 S 和 T 显然是没有前途的,我们考虑 dp ,对于这种有最小瓶颈的问题,我们考虑在笛卡尔树上 dp ,设 \(f_{u,p,0/1}\)表示 dp 到 u 的子树, p 的值是多少, p 是否等于 q ,转移的话做树上背包即可
复杂度 \(O(n^2 \log n)\)(\(\log n\) 是因为要做快速幂,如果预处理可以省去)
#include <bits/stdc++.h>
using namespace std;
int read() {
char c = getchar();
int x = 0;
while(c < '0' || c > '9') c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48,c = getchar();
return x;
}
const int _ = 1e3 + 7;
int f[_][_][2],g[_][2];
int st[_][21];
int lg2[_];int n;int h[_],fa[_],len[_];
int siz[_];
vector<int>E[_];
#define pb push_back
#define mod 998244353
bool cmp(int x,int y) {
if(h[x] == h[y]) return x < y;
return h[x] < h[y];
}
int Min(int x,int y) {
if(cmp(x,y)) return x;
return y;
}
void rmq() {
int t = lg2[n];
for (int i = 1; i <= t; ++i) {
for (int j = 1; j + (1 << i) - 1 <= n; ++j)
st[j][i] = Min(st[j][i-1],st[j+(1<<(i-1))][i-1]);
}
}
int ask(int l,int r) {
int k = lg2[r - l + 1];
return Min(st[l][k],st[r-(1<<k)+1][k]);
}
int build(int l,int r) {
if(l > r) return 0;
int mid = ask(l,r);
int lc = build(l,mid - 1);
int rc = build(mid + 1,r);
if(lc) fa[lc] = mid,E[mid].pb(lc);
if(rc) fa[rc] = mid,E[mid].pb(rc);
len[mid] = r - l + 1;
return mid;
}
void add(int &x,int y) {
x += y - mod;
x += (x >> 31) & mod;
}
int qpow(int x,int y) {
int ans = 1;
while(y) {
if(y & 1) ans = 1ll * ans * x % mod;
x = 1ll * x * x % mod;
y >>= 1;
}
return ans;
}
void treedp(int u) {
f[u][0][1] = 1;/*equal:1,otherwise 0*/
f[u][1][1] = mod - 1;
f[u][1][0] = 1;
int H = h[u] - h[fa[u]];
siz[u] = 1;
for (auto v:E[u]) {
treedp(v);
for (int i = 0; i <= siz[u]; ++i) {
g[i][0] = f[u][i][0];
g[i][1] = f[u][i][1];
f[u][i][0] = f[u][i][1] = 0;
}
for (int i = 0; i <= siz[u]; ++i) {
for (int j = 0; j <= siz[v]; ++j) {
add(f[u][i+j][1],1ll * g[i][1] * f[v][j][1] % mod);
int val = 1ll * (g[i][1] + g[i][0]) % mod * (f[v][j][1] + f[v][j][0]) % mod;
add(val,mod - 1ll * g[i][1] * f[v][j][1] % mod);
add(f[u][i+j][0],val);
}
}
siz[u] += siz[v];
}
for (int p = 0; p <= len[u]; ++p) {
int v = qpow(2,len[u] - p);
f[u][p][1] = 1ll * f[u][p][1] * qpow(v,H) % mod;
add(v,mod - 1);
f[u][p][0] = 1ll * f[u][p][0] * qpow(v,H) % mod;
}
}
int main() {
n = read();
for (int i = 1; i <= n; ++i) h[i] = read();
for (int i = 1; i <= n; ++i) st[i][0] = i;
for (int i = 2; i <= n; ++i) lg2[i] = lg2[i>>1] + 1;
rmq();
int rt = build(1,n);
treedp(rt);
int ans = 0;
for (int p = 0; p <= n; ++p) {
add(ans,(1ll * f[rt][p][0] + f[rt][p][1]) % mod);
}
cout << ans << '\n';
return 0;
}