题解:P9261 [PA 2022] Płótno

闲话:我竟然妄图在考场上调试这坨东西。本一道好题,却生于模拟赛。

题目要求对所有颜色区间 \([l, r]\) 统计连通块个数恰好为 \(v\) 的区间数量。由于 \(k\) 的范围很小,这启发我们用数据结构来整体维护连通块数量。在无环图也就是森林中,有一个非常核心的性质,即连通块数量等于顶点数减去边数。本题的画布是一个包裹在圆柱体上的网格,显然包含大量的环。为了利用上述性质,我们需要在这个图中构造一棵生成森林。

当我们单独考察一个颜色区间 \([l, r]\) 时,一条连接颜色 \(u\) 和 \(v\) 的边是有效的,当且仅当 \(u \ge l\) 且 \(v \ge l\)。也就是说,一条边能够存在的左端点极限是 \(\min(u, v)\)。为了让图保持森林状态,且在区间内连通块数量尽可能少也就是保留的有效边尽可能多,我们需要在存在环的时候,贪心地保留左端点极限最大的边。这个问题自然地转化为了动态维护最大生成树的模型。

我们可以采用扫描线的思想,固定右端点 \(r\) 从 \(1\) 递增到 \(2n\)。用一棵线段树,其底层下标 \(l\) 代表左端点,维护区间 \([l, r]\) 构成的子图中的连通块数量。当扫描线移动到 \(r\) 时,首先加入新顶点,对于所有的 \(l \in [1, r]\),顶点数增加导致连通块数量加一,因此线段树上对应区间的值整体加一。接着考察点 \(r\) 在网格上的相邻点。设邻居颜色为 \(c\),由于是按 \(r\) 递增扫描,必然有 \(c < r\)。这条新边 \((c, r)\) 的边权定义为 \(c\)。

如果加入这条新边没有形成环,它对所有 \(l \in [1, c]\) 都是有效的,使得连通块数量减一,在线段树上将区间 \([1, c]\) 整体减一即可。如果加入新边形成了环,我们需要在环上找到边权最小的那条边,设其边权为 \(w\)。如果 \(w < c\),说明新边的存活范围比旧边更大。此时需要断开旧边,连上新边,这个过程可以通过 Link-Cut Tree 来动态维护。这一断一连的操作意味着,旧边失效导致 \(l \in [1, w]\) 的连通块数量加一,新边生效导致 \(l \in [1, c]\) 的连通块数量减一。依次在线段树上执行这两个区间修改即可。

在扫描线的每一步,我们需要统计线段树中值为 \(1\) 到 \(k\) 的叶子节点个数。普通的线段树在遇到频繁的区间加减时,如果数值整体偏移导致统计数组越界,往往需要进行低效的重构。由于一次区间加减操作不会改变该区间内部各个元素的相对大小,我们不需要在每个线段树节点里维护绝对值的个数。每个节点只需要维护该区间的最小值,以及一个大小为 \(11\) 的数组,表示该区间内值恰好等于最小值加 \(i\) 的元素个数。

在这种设计下,区间加减操作只需要修改区间的最小值以及懒标记,内部的相对差值数组完全不需要变动。上传信息时,父节点的最小值取左右儿子的最小值,然后分别计算左右儿子的最小值与父节点最小值的差值。如果差值在允许的范围内,就把儿子的差值统计数组平移累加到父节点中。最后统计答案时,只需要在代表全局的根节点处,计算目标值与全局最小值的相对差值,提取对应的数量累加即可。这样就将原本可能退化的操作优化到了常数级别。

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
namespace LCT {
	struct Node {
		int father, lson, rson, val, minval, minpos, tag;
	} tree[2500005];
	void pushup(int p) {
		tree[p].minval = tree[p].val;
		tree[p].minpos = p;
		if (tree[p].lson) {
			if (tree[tree[p].lson].minval < tree[p].minval) {
				tree[p].minval = tree[tree[p].lson].minval;
				tree[p].minpos = tree[tree[p].lson].minpos;
			}
		}
		if (tree[p].rson) {
			if (tree[tree[p].rson].minval < tree[p].minval) {
				tree[p].minval = tree[tree[p].rson].minval;
				tree[p].minpos = tree[tree[p].rson].minpos;
			}
		}
	}
	void pushdown(int p) {
		if (tree[p].tag) {
			swap(tree[p].lson, tree[p].rson);
			if (tree[p].lson) {
				tree[tree[p].lson].tag ^= 1;
			}
			if (tree[p].rson) {
				tree[tree[p].rson].tag ^= 1;
			}
			tree[p].tag = 0;
		}
	}
	int identify(int p) {
		int f = tree[p].father;
		if (f && tree[f].lson == p) {
			return 0;
		}
		if (f && tree[f].rson == p) {
			return 1;
		}
		return -1;
	}
	void connect(int p, int f, int op) {
		if (p) {
			tree[p].father = f;
		}
		if (op == 0) {
			tree[f].lson = p;
		} else if (op == 1) {
			tree[f].rson = p;
		}
	}
	void rotate(int p) {
		int x = tree[p].father, y = tree[x].father, opp = identify(p), opx = identify(x), u = (opp == 1) ? tree[p].lson : tree[p].rson;
		connect(u, x, opp);
		connect(x, p, opp ^ 1);
		connect(p, y, opx);
		pushup(x);
		pushup(p);
	}
	void pushall(int p) {
		static int stk[2500005];
		int top = 0;
		stk[++top] = p;
		while (identify(p) != -1) {
			p = tree[p].father;
			stk[++top] = p;
		}
		while (top) {
			pushdown(stk[top]);
			top --;
		}
	}
	void splay(int p) {
		pushall(p);
		while (identify(p) != -1) {
			int u = tree[p].father;
			if (identify(u) == -1) {
				rotate(p);
			} else if (identify(p) == identify(u)) { 
				rotate(u); 
				rotate(p); 
			} else { 
				rotate(p); 
				rotate(p); 
			}
		}
	}
	void access(int p) {
		int u = 0;
		while (p) {
			splay(p);
			tree[p].rson = u;
			pushup(p);
			u = p;
			p = tree[p].father;
		}
	}
	void makeroot(int p) {
		access(p);
		splay(p);
		swap(tree[p].lson, tree[p].rson);
		if (tree[p].lson) {
			tree[tree[p].lson].tag ^= 1;
		}
		if (tree[p].rson) {
			tree[tree[p].rson].tag ^= 1;
		}
	}
	int findroot(int p) {
		access(p);
		splay(p);
		while (tree[p].lson) {
			pushdown(p);
			p = tree[p].lson;
		}
		splay(p);
		return p;
	}
	bool link(int x, int y) {
		makeroot(x);
		if (findroot(y) == x) {
			return false;
		}
		tree[x].father = y;
		return true;
	}
	void cut(int x, int y) {
		makeroot(x);
		access(y);
		splay(y);
		if (tree[y].lson == x && tree[x].rson == 0) {
			tree[y].lson = 0;
			tree[x].father = 0;
			pushup(y);
		}
	}
}
namespace SegmentTree {
	struct Node {
		int minval, tag, cnt[11];
	} tree[2500005];
	void pushup(int p) {
		tree[p].minval = min(tree[p * 2].minval, tree[p * 2 + 1].minval);
		tree[p].cnt[0] = tree[p].cnt[1] = tree[p].cnt[2] = tree[p].cnt[3] = tree[p].cnt[4] = 
		tree[p].cnt[5] = tree[p].cnt[6] = tree[p].cnt[7] = tree[p].cnt[8] = tree[p].cnt[9] = tree[p].cnt[10] = 0;
		int d1 = tree[p * 2].minval - tree[p].minval;
		if (d1 <= 10) {
			for (int i = 0; i <= 10 - d1; i++) {
				tree[p].cnt[d1 + i] += tree[p * 2].cnt[i];
			}
		}
		int d2 = tree[p * 2 + 1].minval - tree[p].minval;
		if (d2 <= 10) {
			for (int i = 0; i <= 10 - d2; i++) {
				tree[p].cnt[d2 + i] += tree[p * 2 + 1].cnt[i];
			}
		}
	}
	void pushdown(int p) {
		if (tree[p].tag != 0) {
			int add = tree[p].tag;
			tree[p * 2].minval += add;
			tree[p * 2].tag += add;
			tree[p * 2 + 1].minval += add;
			tree[p * 2 + 1].tag += add;
			tree[p].tag = 0;
		}
	}
	void buildtree(int p, int l, int r) {
		tree[p].tag = 0;
		if (l == r) {
			tree[p].minval = 0;
			memset(tree[p].cnt, 0, sizeof(tree[p].cnt));
			return;
		}
		int mid = (l + r) >> 1;
		buildtree(p * 2, l, mid);
		buildtree(p * 2 + 1, mid + 1, r);
		pushup(p);
	}
	void change(int p, int l, int r, int ll, int rr, int d) {
		if (ll <= l && r <= rr) {
			tree[p].minval += d;
			tree[p].tag += d;
			return;
		}
		pushdown(p);
		int mid = (l + r) >> 1;
		if (ll <= mid) {
			change(p * 2, l, mid, ll, rr, d);
		}
		if (rr > mid) {
			change(p * 2 + 1, mid + 1, r, ll, rr, d);
		}
		pushup(p);
	}
	void cover(int p, int l, int r, int pos, int val) {
		if (l == r) {
			tree[p].minval = val;
			memset(tree[p].cnt, 0, sizeof(tree[p].cnt));
			tree[p].cnt[0] = 1;
			return;
		}
		pushdown(p);
		int mid = (l + r) >> 1;
		if (pos <= mid) {
			cover(p * 2, l, mid, pos, val);
		} else {
			cover(p * 2 + 1, mid + 1, r, pos, val);
		}
		pushup(p);
	}
}
int n, k, a[3][500005], edgeu[2500005], edgev[2500005], f[1000005];
long long ans[15];
pair<int, int> pos[1000005];
void removeedge(int e) {
	LCT::cut(edgeu[e], e);
	LCT::cut(edgev[e], e);
}
int get_father(int x) {
	return f[x] == x ? x : f[x] = get_father(f[x]);
}
signed main() {
	// freopen("canvas.in", "r", stdin);
	// freopen("canvas.out", "w", stdout);
	ios_base::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin >> n >> k;
	for (int i = 1; i <= n; i ++) {
		cin >> a[1][i];
		pos[a[1][i]] = {1, i};
	}
	for (int i = 1; i <= n; i ++) {
		cin >> a[2][i];
		pos[a[2][i]] = {2, i};
	}
	SegmentTree::buildtree(1, 1, 2 * n);
	for (int i = 1; i <= 2 * n; i ++) {
		f[i] = i;
		LCT::tree[i].val = INF;
		LCT::pushup(i);
	}
	int edgeid = 2 * n;
	for (int r = 1; r <= 2 * n; r ++) {
		if (r > 1) {
			SegmentTree::change(1, 1, 2 * n, 1, r - 1, 1);
		}
		SegmentTree::cover(1, 1, 2 * n, r, 1);
		auto [y, x] = pos[r];
		vector<pair<int, int>> nxt;
		nxt.push_back({3 - y, x});
		nxt.push_back({y, x == 1 ? n : x - 1});
		nxt.push_back({y, x == n ? 1 : x + 1});
		for (auto [ny, nx] : nxt) {
			int c = a[ny][nx];
			if (c < r) {
				int u = r, v = c, fx = get_father(u), fy = get_father(v);
				if (fx != fy) {
					f[fx] = fy;
					int e = ++edgeid;
					edgeu[e] = u; edgev[e] = v;
					LCT::tree[e].val = c;
					LCT::pushup(e);
					LCT::link(u, e);
					LCT::link(e, v);
					SegmentTree::change(1, 1, 2 * n, 1, c, -1);
				} else {
					LCT::makeroot(u);
					LCT::access(v);
					LCT::splay(v);
					int minn = LCT::tree[v].minval, minedge = LCT::tree[v].minpos;
					if (minn < c) {
						removeedge(minedge);
						SegmentTree::change(1, 1, 2 * n, 1, minn, 1);
						int e = ++edgeid;
						edgeu[e] = u; edgev[e] = v;
						LCT::tree[e].val = c;
						LCT::pushup(e);
						LCT::link(u, e);
						LCT::link(e, v);
						SegmentTree::change(1, 1, 2 * n, 1, c, -1);
					}
				}
			}
		}
		for (int i = 1; i <= k; i ++) {
			int diff = i - SegmentTree::tree[1].minval;
			if (diff >= 0 && diff <= 10) {
				ans[i] += SegmentTree::tree[1].cnt[diff];
			}
		}
	}
	for (int i = 1; i <= k; i ++) {
		cout << ans[i] << " ";
	}
	return 0;
}
posted @ 2026-09-03 17:28  xuyifei0302  阅读(8)  评论(0)    收藏  举报