P5327 [ZJOI2019] 语言

P5327 [ZJOI2019] 语言

最开始读错了题, 不然真有可能自己做出来, 遗憾。 考虑一个性质, 每次更新的语言都不一样, 那么这条链上的每个点对都可以互相进行贸易, 那么如果一个点在多条链上, 那它与这些链的所有人都可以贸易, 所以考虑点 x 的贡献, 就是所有覆盖 x 的链构成的最小连通子树大小 - 1。现在有一个很常见的结论。
我们有所有覆盖点 x 链的两端点, 把这些点按 dfs 序排序, 那么有
最小连通子树大小等于 dep(u)+dep(v)dep(lca(u,v)) 最后再减去 dep()
考虑每个点都要算这玩意, 但是每次都暴力统计浪费了太多信息, 再想到哪些信息可以保留, 如果我们把路径加改成差分加子树和, 就可以从儿子继承信息, 并可以得出整个数的答案, 考虑差分可行性, 一条链 (u,v) 可分成 u,lca(u,v), (v,lca(u,v)) 两半, 而这两半分别是 uv 的祖先。
所以可做, 线段树合并即可。
码了一个多小时, 还借助了题解代码, 我真是fw
代码

#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
#define int long long
int n, m, head[N], tot, rt[N];
struct Edge{ int to, next; }edge[N << 1];
void add(int x, int y) {
	edge[++tot] = {y, head[x]}; head[x] = tot;
	edge[++tot] = {x, head[y]}; head[y] = tot;
}
int dfn[N], tim, st[20][N], lg[N * 2] = {-1}, fa[N], dep[N], id[N * 2];
void dfs(int u, int pre) {
	dfn[u] = ++tim, st[0][tim] = u, fa[u] = pre;
	dep[u] = dep[pre] + 1, id[tim] = u;
	for (int i = head[u]; i; i = edge[i].next) {
		int v = edge[i].to;
		if (v == pre) continue;
		dfs(v, u);
		st[0][++tim] = u;
	}
}
void build_st() {
	for (int i = 1; i <= lg[tim]; i++) {
		for (int j = 1; j + (1 << i) - 1 <= tim; j++) {
			int x = st[i - 1][j], y = st[i - 1][j + (1 << (i - 1))];
			st[i][j] = dep[x] < dep[y] ? x : y;
		}
	}
}
int lca(int x, int y) {
	if (x == 0 || y == 0) return 0;
	x = dfn[x], y = dfn[y];
	if (x > y) swap(x, y);
	int k = lg[y - x + 1];
	return dep[st[k][x]] < dep[st[k][y - (1 << k) + 1]] ? st[k][x] : st[k][y - (1 << k) + 1];
}
vector<int> del[N];
#define pb push_back
struct SegT{
	struct Node{
		int ls, rs, mx, mn, sum, is; 
	}t[N * 80];
	int tot;
	#define ls(p) (t[p].ls)
	#define rs(p) (t[p].rs)
	#define mid (l + r >> 1)
	void pushup(int p) {
		t[p].sum = t[ls(p)].sum + t[rs(p)].sum - dep[lca(id[t[ls(p)].mx], id[t[rs(p)].mn])];
		t[p].mn = t[ls(p)].mn ? t[ls(p)].mn : t[rs(p)].mn;
		t[p].mx = t[rs(p)].mx ? t[rs(p)].mx : t[ls(p)].mx;
	}
	void modify(int &p, int l, int r, int x, int y) {
		if (!p) p = ++tot;
		if (l == r) {
			t[p].is += y;		
			if (t[p].is > 0) t[p].mn = t[p].mx = x, t[p].sum = dep[id[x]];
			if (t[p].is <= 0) t[p].mn = t[p].mx = t[p].sum = 0;
			return;
		} 
		if (x <= mid) modify(ls(p), l, mid, x, y);
		if (x > mid) modify(rs(p), mid + 1, r, x, y);
		pushup(p);
	}
	int query(int p) { return t[p].sum - dep[lca(id[t[p].mn], id[t[p].mx])]; }
	void merge(int &p1, int p2, int l, int r) {
		if (!p1 || !p2) return p1 |= p2, void();
		if (l == r) {
			t[p1].is += t[p2].is;
			if (t[p1].is > 0) t[p1].mn = t[p1].mx = l, t[p1].sum = dep[id[l]];
			if (t[p1].is <= 0) t[p1].mn = t[p1].mx = t[p1].sum = 0;;
			return;
		}
		merge(ls(p1), ls(p2), l, mid); merge(rs(p1), rs(p2), mid + 1, r);
		pushup(p1); 
	}
}T;
int ans;
void work(int u, int pre) {
	for (int i = head[u]; i; i = edge[i].next) {	
		int v = edge[i].to;
		if (v == pre) continue;
		work(v, u);
		T.merge(rt[u], rt[v], 1, tim);
	}
	for (auto i : del[u]) T.modify(rt[u], 1, tim, dfn[i], -1);
	ans += T.query(rt[u]);
}
signed main() {
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= n * 2; i++) lg[i] = lg[i / 2] + 1;
	for (int i = 1, x, y; i <= n - 1; i++) {
		scanf("%lld%lld", &x, &y);
		add(x, y);
	}
	dfs(1, 0); build_st(); 
	for (int i = 1, u, v, _lca; i <= m; i++) {
		scanf("%lld%lld", &u, &v); _lca = lca(u, v);
		del[_lca].pb(u); del[_lca].pb(v);
		del[fa[_lca]].pb(u); del[fa[_lca]].pb(v);
		T.modify(rt[u], 1, tim, dfn[u], 1);
		T.modify(rt[u], 1, tim, dfn[v], 1);
		T.modify(rt[v], 1, tim, dfn[v], 1);
		T.modify(rt[v], 1, tim, dfn[u], 1);
	} 
	work(1, 0); printf("%lld", ans / 2);
	return 0;
}
posted @   qqrj  阅读(14)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示