POJ_2987
对于这个题目的第一问,无论是放大边权的做法还是dfs找和S连通的做法,限于个人水平有限,暂时对两个做法都没有证明出来是对的,但个人更倾向于第二种方式。
在Edelweiss的《网络流建模汇总》中有提到过如何判断最小割是否唯一。之所以出现最小割不唯一,一部分是源于类似这样的情况:S到1有条容量为1的边,1到T也有条容量为1的边,做完最小割之后两条边的残量都变成0了,也就是说都可以看作是割边,那么1既可以看作是和S相连,也可以看作是和T相连,就这个题目而言是希望和S连的点越少越好,那么一旦出现这种情况就统统认为那个点是和T相连的,因此我们从S开始dfs遇到残量为0的边就不再继续往下走了,因为默认后面那些点是和T连着的。
当然上面这种说明只是针对一个点而言的,究竟能不能推广到所有情况还是有待证明的。
综上,第一问我也是糊里糊涂的,接下来还是分析清醒一点的第二问吧。
处理第二问我大致想到两种思路:
① 从转化成最大权闭合图模型入手:这个题目实际上期望得到一个firing的集合,使得这个集合中没有任何一个点存在一条指向不在这个集合中的点的边,也就是说我们要得到一个闭合图,那么又希望这个集合的点权和最大,自然就是最大权闭合图的模型了。最大权闭合图模型的建图可以参考07年国家集训队论文《最小割模型在信息学竞赛中的应用》。
② 从构造最小割入手:我们需要得到一个firing的集合,还有一个不被firing的集合,而且要满足不存在任何一条从firing的集合中发出的指向不被firing的集合的边,那么如果最终将从S出发能到达的点看做firing的集合中的点,其他的能到T的点看作不被firing的集合中的点,那么在做完最大流之后自然就不会有firing集合中的点指向不被firing的集合中的点的边了,否则就会形成增广路。那么为了能够使得到这个割图的代价最小,那么就要求最小割,也就是相对于最大获利来讲,我们要让损失尽量少。接着就要考虑每个点如何和S、T连边了,这样就要考虑每个点放到S还是放到T中的得与失。对于正权(不妨假设值为x)的点,如果放到T中,相对于最大获利来讲就相当于损失了x,也就是说如果这个点和S断开了联系,那么就应该付出x的代价,因此连一条S到这个点的容量为x的边,这样如果这个边成为了割边,那么这个点就会在T这个集合中,由于流满了这条边就相当于付出了x的代价。对于负权的点,分析起来也是类似的,就不再赘述了。
此外,这个题目在连INF的边的时候要用long long,而且INF要足够大,否则因为题目中bi的范围比较大,INF这条边就可能满流,如果INF的边都满流了肯定就不能称为容量是INF的边了……
#include<stdio.h> #include<string.h> #include<algorithm> #define MAXD 5010 #define MAXM 130010 #define INF 0x3f3f3f3f3f3f3f3fll int N, M, first[MAXD], e, next[MAXM], u[MAXM], v[MAXM]; int S, T, d[MAXD], q[MAXD], work[MAXD]; long long TOT, flow[MAXM]; void add(int x, int y, long long z) { u[e] = x, v[e] = y, flow[e] = z; next[e] = first[x], first[x] = e ++; } void init() { int i, x, y; S = 0, T = N + 1; memset(first, -1, sizeof(first[0]) * (T + 1)), e = 0; TOT = 0; for(i = 1; i <= N; i ++) { scanf("%d", &x); if(x > 0) { TOT += x; add(S, i, x), add(i, S, 0); } else if(x < 0) add(i, T, -x), add(T, i, 0); } for(i = 0; i < M; i ++) { scanf("%d%d", &x, &y); add(x, y, INF), add(y, x, 0); } } int bfs() { int i, j, rear = 0; memset(d, -1, sizeof(d[0]) * (T + 1)); d[S] = 0, q[rear ++] = S; for(i = 0; i < rear; i ++) for(j = first[q[i]]; j != -1; j = next[j]) if(flow[j] && d[v[j]] == -1) { d[v[j]] = d[q[i]] + 1, q[rear ++] = v[j]; if(v[j] == T) return 1; } return 0; } long long dfs(int cur, long long a) { if(cur == T) return a; for(int &i = work[cur]; i != -1; i = next[i]) if(flow[i] && d[v[i]] == d[cur] + 1) if(long long t = dfs(v[i], std::min(a, flow[i]))) { flow[i] -= t, flow[i ^ 1] += t; return t; } return 0; } long long dinic() { long long ans = 0, t; while(bfs()) { memcpy(work, first, sizeof(first[0]) * (T + 1)); while(t = dfs(S, INF)) ans += t; } return ans; } void DFS(int cur, int &num) { int i; d[cur] = 1, ++ num; for(i = first[cur]; i != -1; i = next[i]) if(flow[i] && !d[v[i]]) DFS(v[i], num); } void solve() { int num = 0; long long ans = TOT - dinic(); memset(d, 0, sizeof(d[0]) * (T + 1)); DFS(S, num); printf("%d %lld\n", num - 1, ans); } int main() { while(scanf("%d%d", &N, &M) == 2) { init(); solve(); } return 0; }