QBXTOJ 3873: 树

给定一棵 n 个结点的树,结点标号 1∼n 。你需要给每个点标注 0 或 1 的权值。
有若干条规则,形如 “若路径 u−v 上所有点权都是 c ,则你可获得 p 的收益” ,问最大可能的总收益。
其中c∈{0, 1}.

对每一条 c = 0 的路径和每一条 c = 1 的路径,判断它们是否冲突。
建出二分图,转化为求其最大权独立集。
用最小割求解即可。

树上 a->b 的路径与 c->d 的路径相交,等价于 lca(a, b) 在 c->d 的路径上或 lca(c, d) 在 a->b 的路径上。
树上 p 在 a->b的路径上,等价于 dist(a, p) + dist(p, b) = dist(a, b).

代码如下:

#include <bits/stdc++.h>

using namespace std;

inline int read() {
	int x = 0, f = 0; char ch = getchar();
	while (!isdigit(ch)) f = ch == '-', ch = getchar();
	while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
	return f ? -x : x;
}

const int N = 100010, M = 200010, AA = 710, inf = 0x3f3f3f3f; 
const int NN = 1410, MM = 1000100; 
int sum, n, A, B, thead[N], tnex[M], tver[M], ttot = 1; 
int dep[N], fa[N][20];
struct Node {
	int u, v, p, fa; 
};
Node a[AA], b[AA]; 
struct Graph {
	int S, T, head[NN], nex[MM], ver[MM], f[MM], tot = 1;
	int d[NN], cur[NN]; 
	bool bfs() {
		memset(d, 0, sizeof d); 
		queue<int> q; 
		q.push(S); d[S] = 1; cur[S] = head[S]; 
		while (!q.empty()) {
			int x = q.front(); q.pop(); 
			for (int i = head[x]; i; i = nex[i]) {
				int y = ver[i]; 
				if (f[i] && !d[y]) {
					d[y] = d[x] + 1; 
					cur[y] = head[y]; 
					if (y == T) return true; 
					q.push(y); 
				}
			}
		}
		return false; 
	} 
	int find(int u, int lim) {
		if (u == T) return lim; 
		int flow = 0; 
		for (int i = cur[u]; i && flow < lim; i = nex[i]) {
			cur[u] = i; 
			int y = ver[i];
			if (f[i] && d[y] == d[u] + 1) {
				int z = find(y, min(lim - flow, f[i])); 
				if (!z) d[y] = 0; 
				else f[i] -= z, f[i ^ 1] += z, flow += z; 
			} 
		}
		return flow; 
	}
	int dinic() {
		int res = 0, flow; 
		while (bfs()) while ((flow = find(S, inf))) res += flow; 
		return res; 
	}
	void add(int x, int y, int z) {
		ver[++tot] = y; f[tot] = z; nex[tot] = head[x]; head[x] = tot; 
		ver[++tot] = x; f[tot] = 0; nex[tot] = head[y]; head[y] = tot;
	}
};
Graph G; 

void Add(int x, int y) { tver[++ttot] = y; tnex[ttot] = thead[x]; thead[x] = ttot; }

void dfs_lca(int x, int father) {
	dep[x] = dep[father] + 1; 
	fa[x][0] = father; 
	for (int j = 1; j < 20; ++j) {
		fa[x][j] = fa[fa[x][j - 1]][j - 1]; 
	}
	for (int i = thead[x]; i; i = tnex[i]) {
		int y = tver[i]; 
		if (y == father) continue; 
		dfs_lca(y, x); 
	}
}

int lca(int x, int y) {
	if (dep[x] > dep[y]) swap(x, y); // dep[x] <= dep[y]; 
	int depth = dep[y] - dep[x]; 
	for (int j = 19; j >= 0; --j) {
		if ((depth >> j) & 1) {
			y = fa[y][j]; 
		}
	}
	if (x == y) return x; 
	for (int j = 19; j >= 0; --j) {
		if (fa[x][j] != fa[y][j]) {
			x = fa[x][j]; y = fa[y][j]; 
		}
	}
	return fa[x][0]; 
}

int dist(int x, int y) {
	int p = lca(x, y); 
	return dep[x] + dep[y] - dep[p] * 2; 
}

bool judge(int x, int y) {
	int aa = a[x].u, bb = a[x].v, p1 = a[x].fa; 
	int c = b[y].u, d = b[y].v, p2 = b[y].fa; 
	if (dist(aa, p2) + dist(p2, bb) == dist(aa, bb) || dist(c, p1) + dist(p1, d) == dist(c, d)) return true; 
	return false; 
}

int main() {
	n = read(); A = read(); B = read(); G.S = NN - 1; G.T = NN - 2; 
	for (int i = 1; i < n; ++i) {
		int u = read(), v = read(); 
		Add(u, v); Add(v, u); 
	}
	dfs_lca(1, 0); 
	for (int i = 1; i <= A; ++i) {
		a[i].u = read(); a[i].v = read(); a[i].p = read(); 
		a[i].fa = lca(a[i].u, a[i].v); sum += a[i].p;
		G.add(G.S, i, a[i].p); 
	}
	for (int i = 1; i <= B; ++i) {
		b[i].u = read(); b[i].v = read(); b[i].p = read(); 
		b[i].fa = lca(b[i].u, b[i].v); sum += b[i].p;
		G.add(A + i, G.T, b[i].p); 
	}
	for (int i = 1; i <= A; ++i) {
		for (int j = 1; j <= B; ++j) {
			if (judge(i, j)) {
				G.add(i, A + j, inf); 
			}
		}
	}
	printf("%d\n", sum - G.dinic()); 
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值