[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;
}
posted @ 2021-06-04 22:55  y_dove  阅读(153)  评论(0编辑  收藏  举报