Loading

[题解] 树(tree)

题目大意

​ 给定一颗 \(N\) 个点的有根树,其中 \(1\) 是树根,除了 \(1\) 以外的其他点 \(u\) 有唯一的父亲 \(Father_u\)。同时,给定 \(M\) 条路径,第 \(i\) 条路径是 \((s_i,t_i)\),保证 \(s_i\)\(t_i\) 的祖先。你可以任意选择若干条路径。如果第 \(i\) 条路径被选择,那么你可以得到 \(V_i\) 的收益,这里保证 \(V_i\) 是非负的。

​ 同时,对于每个不是 \(1\) 的点 \(u\),考虑其连接它的父亲 \(Father_u\) 的边,如果有 \(x\) 条覆盖了它且被选择的路径,需要花费代价 \(A_u\times x^2+B_u\times x\),这里也保证 \(A_u\)\(B_u\) 是非负的。代价的含义就是收益要减去这么多,相当于负的收益。

​ 你需要求出在以上条件下你的最大收益。

数据范围:\(1\leq N,M\leq 150\)\(\forall2\leq u\leq N,0\leq A_u \leq 100,0\leq B_u \leq 10000\)\(\forall 1\leq i \leq M,0\leq V_i \leq 10^6\),并保证 \(s_i\) 总是 \(t_i\) 的祖先。

时间限制:1000ms

空间限制:1024MB

解题思路

题解直接给出了费用流的建图方案,然后证明正确性,这里还是想想要如何想到使用费用流。

数据范围只是一个小点,不过确实可以考虑按照数据范围整理一个常用算法表。

关键点应该在于费用是二次的,像这种似乎是很不好处理的,如果是全局二次的话,大概还可以二分或者枚举一下次数,但是这是每条边都是一个二次费用,所以目前所知道的算法里,只有费用流按每次连边能实现这一点,因为对于费用的贪心可以保证按照需要的顺序流过这些流。

于是就考虑建图。费用流有一种常见的做法是先考虑全部都选择,然后计算需要减去的费用, 那么要么是减去 \(v_i\) 直接不选,要么是减去路上的费用,按照这个思路建图就好了。

#include <bits/stdc++.h>
using namespace std;

const int N(155), E(N * (N + 3)), INF(1e9);

int n, m, fa[N], a[N], b[N], s[N], t[N], v[N];
int ans;
int cnt = 1, head[N];
int S, T, dcnt, dis[N], flo[N], pre[N], inq[N];
struct edge{ int nx, to, v, f; } e[E << 1];

inline void read(int &x){
	x = 0; int f = 1, c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)) x = x * 10 + c - 48, c = getchar();
	x *= f;
}

inline void input(){
	read(n), read(m);
	for(int i(2); i <= n; ++i) read(fa[i]), read(a[i]), read(b[i]);
	for(int i(1); i <= m; ++i) read(s[i]), read(t[i]), read(v[i]);
}

inline void add(int u, int v, int w, int f){
	e[++cnt] = {head[u], v, w, f}, head[u] = cnt;
	e[++cnt] = {head[v], u, -w, 0}, head[v] = cnt;
}
inline void prep(){
	S = n + 1, T = 1, dcnt = n + 1;
	for(int i(2); i <= n; ++i)
		for(int j(1); j <= m; ++j) add(i, fa[i], (j * 2 - 1) * a[i] + b[i], 1);
	for(int i(1); i <= m; ++i)
		add(S, t[i], 0, 1), add(s[i], T, 0, 1), add(t[i], s[i], v[i], 1), ans += v[i];
}

bool spfa(){
	static queue <int> Q;
	for(int i(1); i <= dcnt; ++i) dis[i] = INF;
	dis[S] = 0, flo[S] = INF, Q.push(S), inq[S] = 1;
	while(!Q.empty()){
		int x = Q.front(); Q.pop(), inq[x] = 0;
		for(int i(head[x]); i; i = e[i].nx){
			int y = e[i].to;
			if(dis[y] > dis[x] + e[i].v && e[i].f){
				dis[y] = dis[x] + e[i].v; 
				flo[y] = min(flo[x], e[i].f);
				pre[y] = i;
				if(!inq[y]) Q.push(y), inq[y] = 1;
			}
		}
	}
	return dis[T] != INF;
}
inline void work(){
	int mf = 0, mc = 0;
	while(spfa()){
		mf += flo[T], mc += flo[T] * dis[T];
		int now = T;
		while(now){
			now = pre[now];
			e[now].f -= flo[T], e[now ^ 1].f += flo[T];
			now = e[now ^ 1].to;
		}
	}
	ans -= mc;
}

inline void output(){ printf("%d\n", ans); }

int main(){
	freopen("tree.in", "r", stdin);
	freopen("tree.out", "w", stdout);
	input();
	prep();
	work();
	output();
	return 0;
}
posted @ 2021-09-17 09:44  IrisT  阅读(42)  评论(0编辑  收藏  举报