[题解] 树(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;
}