网络流水题五杀——洛谷 P3701、P2472、P1129、P2053、P2805
来源:互联网 发布:特价机票全额退款 知乎 编辑:程序博客网 时间:2024/06/18 12:24
前言
前段时间,趁着NOIP爆炸后,狂补文化课的空隙,一口气刷了好多网络流的shui ti……
[1星难度] 洛谷 P3701「伪模板」主席树 (二分图多重匹配)
题目传送门
题解
很明显,有两个人,所以是二分图,两两对决可以看作匹配,每个人可以和多个人对决,因此是二分图的多重匹配。我们将s连向byx的人,容量为每个人的寿命。然后手气君的人连向t,容量为寿命。这里提前算好长者续命后的寿命。然后从左向右,当A能打过B时,连一条边,容量为1。最后跑一遍最大流,然后由于只能打m场,要和m取个min。
坑点:两个字符串直接比较是否相等是不行的,我们拿第一个字符出来比才是珂学的。
代码
#include <cstdio>#include <cstdlib>#include <iostream>#include <cstring>#include <algorithm>#include <cmath>#define maxn 100010#define maxm 1000010#define INF 0x7FFFFFFFusing namespace std;int n, m, s, t, cur = -1, level[maxn], q[maxn], life1[maxn], life2[maxn];char byx[maxn][5], sqj[maxn][5];struct List{ int obj, cap; List *next, *rev;}Edg[maxm], *head[maxn], *iter[maxn];void Addedge(int a, int b, int c){ Edg[++cur].next = head[a]; Edg[cur].obj = b; Edg[cur].cap = c; Edg[cur].rev = Edg+(cur^1); head[a] = Edg+cur;}bool bfs(){ int low = 0, high = 0; q[0] = s; for(int i = s; i <= t; i++) level[i] = -1; level[s] = 0; while(low <= high){ int now = q[low++]; for(List *p = head[now]; p; p = p->next){ int v = p->obj, c = p->cap; if(c && level[v] == -1){ level[v] = level[now] + 1; q[++high] = v; } } } return level[t] != -1;}int Dinic(int now, int f){ if(now == t || !f) return f; int ret = 0; for(List *&p = iter[now]; p; p = p->next){ int v = p->obj, c = p->cap; if(c && level[v] > level[now]){ int d = Dinic(v, min(f, c)); f -= d; p->cap -= d; ret += d; p->rev->cap += d; if(!f) break; } } return ret;}int MaxFlow(){ int flow = 0; while(bfs()){ for(int i = s; i <= t; i++) iter[i] = head[i]; flow += Dinic(s, INF); } return flow;}int main(){ scanf("%d%d", &n, &m); int cnt1 = 0, cnt2 = 0; for(int i = 1; i <= n; i++){ scanf("%s", byx[i]); if(byx[i][0] == 'Y') cnt1 ++; } for(int i = 1; i <= n; i++){ scanf("%s", sqj[i]); if(sqj[i][0] == 'Y') cnt2 ++; } for(int i = 1; i <= n; i++){ scanf("%d", &life1[i]); if(byx[i][0] == 'J') life1[i] += cnt1; } for(int i = 1; i <= n; i++){ scanf("%d", &life2[i]); if(sqj[i][0] == 'J') life2[i] += cnt2; } s = 1; t = s + n + n + 1; for(int i = s; i <= t; i++) head[i] = NULL; for(int i = 1; i <= n; i++){ Addedge(s, s+i, life1[i]); Addedge(s+i, s, 0); } for(int i = 1; i <= n; i++){ Addedge(s+n+i, t, life2[i]); Addedge(t, s+n+i, 0); } for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++){ if(byx[i][0] == 'W' && (sqj[j][0] == 'Y' || sqj[j][0] == 'E')){ Addedge(s+i, s+n+j, 1); Addedge(s+n+j, s+i, 0); } if(byx[i][0] == 'J' && (sqj[j][0] == 'W' || sqj[j][0] == 'H')){ Addedge(s+i, s+n+j, 1); Addedge(s+n+j, s+i, 0); } if(byx[i][0] == 'E' && (sqj[j][0] == 'Y' || sqj[j][0] == 'J')){ Addedge(s+i, s+n+j, 1); Addedge(s+n+j, s+i, 0); } if(byx[i][0] == 'Y' && (sqj[j][0] == 'H' || sqj[j][0] == 'J')){ Addedge(s+i, s+n+j, 1); Addedge(s+n+j, s+i, 0); } if(byx[i][0] == 'H' && (sqj[j][0] == 'E' || sqj[j][0] == 'W')){ Addedge(s+i, s+n+j, 1); Addedge(s+n+j, s+i, 0); } } printf("%d\n", min(MaxFlow(), m)); return 0;}
[1星难度] 洛谷 P2472 [SCOI2007]蜥蜴 (最大流)
题目传送门
题解
无法逃离的最小,不如求可以逃离的最大。我们由s向每个有蜥蜴的格子连边,容量为1,然后每个格子向可以到达的格子连边,容量为INF,但是每个格子有容量限制,于是我们拆点,格子分为入与出,格子间又出向入连,容量依旧INF,每个格子内部入向出连,容量为限制。然后如果一个格子能跳出地图的话,就向t连边,容量为INF。然后跑一遍最大流就是最多能跑掉的蜥蜴了。用总数减之就行了。好简单啊。构图一堆细节,容易敲错。
代码
#include <iostream>#include <algorithm>#include <cmath>#include <cstring>#include <cstdio>#include <cstdlib>#define maxn 100010#define maxm 1000010#define N 100#define INF 0x7FFFFFFFusing namespace std;int R, C, D, s, t, Sum;int cur = -1, level[maxn], q[maxn];char m1[N][N], m2[N][N];struct List{ int obj, cap; List *next, *rev;}Edg[maxm], *head[maxn], *iter[maxn];void Addedge(int a, int b, int c){ Edg[++cur].next = head[a]; Edg[cur].obj = b; Edg[cur].cap = c; Edg[cur].rev = Edg+(cur^1); head[a] = Edg+cur;}bool bfs(){ int low = 0, high = 0; q[0] = s; for(int i = s; i <= t; i++) level[i] = -1; level[s] = 0; while(low <= high){ int now = q[low++]; for(List *p = head[now]; p; p = p->next){ int v = p->obj, c = p->cap; if(!c || level[v] != -1) continue; level[v] = level[now] + 1; q[++high] = v; } } return level[t] != -1;}int Dinic(int now, int f){ if(now == t || !f) return f; int ret = 0; for(List *&p = iter[now]; p; p = p->next){ int v = p->obj, c = p->cap; if(c && level[v] > level[now]){ int d = Dinic(v, min(f, c)); f -= d; p->cap -= d; p->rev->cap += d; ret += d; if(!f) break; } } return ret;}int MaxFlow(){ int flow = 0; while(bfs()){ for(int i = s; i <= t; i++) iter[i] = head[i]; flow += Dinic(s, INF); } return flow;}int main(){ scanf("%d%d%d", &R, &C, &D); for(int i = 1; i <= R; i++) scanf("%s", m1[i]); for(int i = 1; i <= R; i++) scanf("%s", m2[i]); s = 1; t = s + R * C * 2 + 1; for(int i = s; i <= t; i++) head[i] = NULL; for(int i = 1; i <= R; i++) for(int j = 1; j <= C; j++){ if(m2[i][j-1] == 'L'){ Sum ++; Addedge(s, s+(i-1)*C+j, 1); Addedge(s+(i-1)*C+j, s, 0); } } for(int i = 1; i <= R; i++) for(int j = 1; j <= C; j++){ for(int k = 0; k <= D; k++) for(int w = 0; w <= D; w++){ if(k + w < 1 || k + w > D) continue; if(i + k > R || j + w > C || i - k < 1 || j - w < 1){ Addedge(s+R*C+(i-1)*C+j, t, INF); Addedge(t, s+R*C+(i-1)*C+j, 0); } if(i + k <= R && j + w <= C){ Addedge(s+R*C+(i-1)*C+j, s+(i+k-1)*C+j+w, INF); Addedge(s+(i+k-1)*C+j+w, s+R*C+(i-1)*C+j, 0); } if(i + k <= R && j - w > 0){ Addedge(s+R*C+(i-1)*C+j, s+(i+k-1)*C+j-w, INF); Addedge(s+(i+k-1)*C+j-w, s+R*C+(i-1)*C+j, 0); } if(i - k > 0 && j + w <= C){ Addedge(s+R*C+(i-1)*C+j, s+(i-k-1)*C+j+w, INF); Addedge(s+(i-k-1)*C+j+w, s+R*C+(i-1)*C+j, 0); } if(i - k > 0 && j - w > 0){ Addedge(s+R*C+(i-1)*C+j, s+(i-k-1)*C+j-w, INF); Addedge(s+(i-k-1)*C+j-w, s+R*C+(i-1)*C+j, 0); } } } for(int i = 1; i <= R; i++) for(int j = 1; j <= C; j++){ Addedge(s+(i-1)*C+j, s+R*C+(i-1)*C+j, m1[i][j-1]-'0'); Addedge(s+R*C+(i-1)*C+j, s+(i-1)*C+j, 0); } printf("%d\n", Sum - MaxFlow()); return 0;}
[2星难度] 洛谷 P1129 [ZJOI2007]矩阵游戏 (二分图匹配)
题目传送门
题解
这题就是个裸的二分图匹配,之所以是2星难度,是因为转化模型不是特别好想。首先,我们考虑每一行,很容易看出,每一行最终只能保留一列作为最后起作用的那一个。然后我们发现如果某一行啥都没有就肯定是No。我们想到了?匹配。我们再分析一下。如果每一行归属的列确定了,列的任意交换是没有任何关系的,而且每个行选中的列不能相同,这样就一定能满足题目要求。行列一一对应。于是考虑行、列的二分图匹配。
严谨一点,每个二分图匹配的答案必然可以成为答案,因为只要交换行就行了;每个答案也必然满足二分图匹配,若不满足就显然不是答案。我们交换行或列在二分图上没有任何关系,于是成功将问题转化,按题目连边然后直接看看二分图是否满流就行了。
这题其实还算是巧妙的,最后能转换为二分图模型的题目有很多,格子行和列(x,y坐标),格子黑白染色,奇偶性等等都可能迁移过来,GDKOI2017某一道题好像有些类似。。
代码
#include <iostream>#include <cstdio>#include <cstdlib>#include <cmath>#include <algorithm>#include <cstring>#define maxn 100010#define maxm 1000010#define N 205#define INF 0x7FFFFFFFusing namespace std;int T, n, s, t, A[N][N], level[maxn], iter[maxn];int cur, head_p[maxn], q[maxn];struct List{ int next, obj, cap;}Edg[maxm];void Addedge(int a, int b, int c){ Edg[++cur].next = head_p[a]; Edg[cur].obj = b; Edg[cur].cap = c; head_p[a] = cur;}bool bfs(){ q[0] = s; int head = 0, tail = 0; for(int i = s; i <= t; i++) level[i] = -1; level[s] = 0; while(head <= tail){ int now = q[head++]; for(int i = head_p[now]; ~ i; i = Edg[i].next){ int v = Edg[i].obj, c = Edg[i].cap; if(c && level[v] == -1){ q[++tail] = v; level[v] = level[now] + 1; } } } return level[t] != -1;}int Dinic(int now, int f){ if(now == t || !f) return f; int ret = 0; for(int &i = iter[now]; ~ i; i = Edg[i].next){ int v = Edg[i].obj, c = Edg[i].cap; if(c && level[now] < level[v]){ int d = Dinic(v, min(c, f)); ret += d; f -= d; Edg[i].cap -= d; Edg[i^1].cap += d; if(!f) break; } } return ret;}int MaxFlow(){ int flow = 0; while(bfs()){ for(int i = s; i <= t; i++) iter[i] = head_p[i]; flow += Dinic(s, INF); } return flow;}int main(){ scanf("%d", &T); while(T --){ scanf("%d", &n); s = 1; t = s + n * 2 + 1; for(int i = s; i <= t; i++) head_p[i] = -1; cur = -1; for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++){ scanf("%d", &A[i][j]); if(A[i][j]){ Addedge(s+i, s+n+j, INF); Addedge(s+n+j, s+i, 0); } } for(int i = 1; i <= n; i++){ Addedge(s, s+i, 1); Addedge(s+i, s, 0); Addedge(s+n+i, t, 1); Addedge(t, s+n+i, 0); } if(MaxFlow() == n) puts("Yes"); else puts("No"); } return 0;}
[3星难度] 洛谷 P2053 [SCOI2007]修车 (最小费用最大流)
题目传送门
题解
这题我一开始想着将人拆成T个点,然后跑费用流,但是发现这样有点迷。处理了单独的费用,但是如何处理连边限制好像很难搞。于是我就从另一个角度入手,如果我知道每个人是修到第几辆车不就行了?于是我考虑将一个人拆成n个人,分别代表一个人在n个时间段的状态。一个人可能要先修某一辆车再去修另一辆,这使贡献的等待时间不同。但我们这样连边就解决了一切问题。这样修一辆车时,我们可以算出这个车主的等待时间,而且保证修车同时进行,在修一辆车的时候也不会有人去打扰他(流量限制)。于是答案就是最小的等待时间除以人数,而要求全部修完,就是最小费用最大流了。这题不能贪心能修则修,因为可以等待最快的人修完其他车才轮到你。
本题就是容量为1的费用流。连边就是s向每辆车连容量1,费用0。中间全部连(流出最佳方案),容量INF,费用为对应的时间乘时间段(修时也在等),这是这条流(即这个车主等待)的费用。我们不能算出这辆车对所有人的贡献,因为除了知道车主还在之外不能知道其他人走了没。这也告诉我们费用流要单独考虑一条流的费用。然后每个拆出来的人向t连边,容量1,费用0。跑一遍就得到答案了。
好久没写费用流,但是居然一次就写对了,真感动。(不就是个最短路吗,代码比Dinic短啊)
代码
#include <cstdio>#include <iostream>#include <algorithm>#include <cmath>#include <cstdlib>#include <cstring>#define maxn 100010#define maxm 1000010#define N 100#define INF 0x7FFFFFFFusing namespace std;int m, n, s, t, cur = -1;int head_p[maxn], q[maxn], preV[maxn], preE[maxn], flow[maxn], dis[maxn], T[N][N];bool vis[maxn];struct List{ int next, obj, cost, cap;}Edg[maxm];void Addedge(int a, int b, int c, int d){ Edg[++cur].next = head_p[a]; Edg[cur].obj = b; Edg[cur].cap = c; Edg[cur].cost = d; head_p[a] = cur;}bool SPFA(){ for(int i = s; i <= t; i++) dis[i] = INF, vis[i] = false; q[0] = s; vis[s] = true; flow[s] = INF; dis[s] = 0; int head = 0, tail = 0; while(head <= tail){ int now = q[head%maxn]; head ++; for(int i = head_p[now]; ~ i; i = Edg[i].next){ int v = Edg[i].obj, c = Edg[i].cap, d = Edg[i].cost; if(!c) continue; if(dis[now] + d < dis[v]){ dis[v] = dis[now] + d; flow[v] = min(flow[now], c); preE[v] = i; preV[v] = now; if(!vis[v]){ vis[v] = true; tail ++; q[tail%maxn] = v; if(dis[q[head%maxn]] > dis[q[tail%maxn]]) swap(q[head%maxn], q[tail%maxn]); } } } vis[now] = false; } return dis[t] != INF;}int MaxFlowMinCost(){ int res = 0; while(SPFA()){ res += dis[t] * flow[t]; for(int i = t; i != s; i = preV[i]){ Edg[preE[i]].cap -= flow[t]; Edg[preE[i]^1].cap += flow[t]; } } return res;}int main(){ scanf("%d%d", &m, &n); s = 1; t = s + n + m * n + 1; for(int i = s; i <= t; i++) head_p[i] = -1; for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d", &T[i][j]); for(int i = 1; i <= n; i++){ Addedge(s, s+i, 1, 0); Addedge(s+i, s, 0, 0); } for(int i = 1; i <= m; i++) for(int j = 1; j <= n; j++){ Addedge(s+n+(i-1)*n+j, t, 1, 0); Addedge(t, s+n+(i-1)*n+j, 0, 0); } for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) for(int k = 1; k <= n; k++){ Addedge(s+i, s+n+(j-1)*n+k, INF, T[i][j]*k); Addedge(s+n+(j-1)*n+k, s+i, 0, -T[i][j]*k); } printf("%.2lf\n", 1.00 * MaxFlowMinCost() / n); return 0;}
[3星难度] 洛谷 P2805 植物大战僵尸 (最大权闭合子图+拓扑排序)
题目传送门
题解
终于有了压轴题的感觉。其实这题就是果的最大权闭合子图。APIO时听大神讲过的,当时记得很清楚,但是好像很难写。现在写了一下发现并不难。
首先吃了保护别人的植物才能吃别人,于是就是拿了A就必须拿B的模型,而如果全是正权能拿的拿完就好了,有负权就必须有所取舍的去决策。于是就套模型。将s连向正权植物,容量为权值,如果割掉代表失去正权。负权植物连向t,容量为权值相反数,如果割掉代表获得损失。每个植物向保护它的连边,容量为INF,代表选了这个就要选下一个,最小割割不断它们。
这样s集合代表选,t集合代表不选,中间不能割掉,而且任何一条s-t路径都会导致边的状态不确定,又拿了正权,又不想吃负权,于是这样的路径非法,要被割掉。于是割与方案就对应了。答案就是正权和-最小割。
但是,没有这么简单的哦,植物之间的保护可能成环,这时我们根本就不能获取它们的任何权值,甚至连向这个团的点的权值也不能拿到。我们需要来个拓扑排序搞掉所有的环以及连向环的点。这里不需要Tarjan,假如我们将边反向,拓扑一遍没标记的点就不会在网络里。为什么呢?因为拓扑排序能访问到某点的充要条件是它的入度可能变成0,而一个环无论怎样(除非缩起来),入度是不会通过其它边改成0的,而环搜不到,那环所指向的点的入度也不会变成0了。这些处于“铜墙铁壁”保护的植物就拥有不死之躯了。如此便与图完全对应了。
拓扑时边一定要反向!一开始错在这里。
代码
#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <iostream>#include <cmath>#define maxn 100100#define maxm 1000100#define INF 0x7FFFFFFFusing namespace std;bool ban[maxn];int n, m, s, t;int SUM, in[maxn], head_p[maxn], cnt = -1, cur = -1, level[maxn], iter[maxn], q[maxn], sco[maxn];struct EDGE{ EDGE *next; int obj;}E[maxm], *HEAD[maxn];struct List{ int next, obj, cap;}Edg[maxm];void Insert(int a, int b){ E[++cnt].next = HEAD[a]; E[cnt].obj = b; HEAD[a] = E+cnt;}void Addedge(int a, int b, int c){ Edg[++cur].next = head_p[a]; Edg[cur].obj = b; Edg[cur].cap = c; head_p[a] = cur;}bool bfs(){ int head = 0, tail = 0; q[0] = s; for(int i = s; i <= t; i++) level[i] = -1; level[s] = 0; while(head <= tail){ int now = q[head++]; for(int i = head_p[now]; ~ i; i = Edg[i].next){ int v = Edg[i].obj, c = Edg[i].cap; if(!c || level[v] != -1) continue; level[v] = level[now] + 1; q[++tail] = v; } } return level[t] != -1;}int Dinic(int now, int f){ if(now == t || !f) return f; int ret = 0; for(int &i = iter[now]; ~ i; i = Edg[i].next){ int v = Edg[i].obj, c = Edg[i].cap; if(!c || level[v] <= level[now]) continue; int d = Dinic(v, min(f, c)); ret += d; f -= d; Edg[i].cap -= d; Edg[i^1].cap += d; if(!f) break; } return ret;}int MinCut(){ int flow = 0; while(bfs()){ for(int i = s; i <= t; i++) iter[i] = head_p[i]; flow += Dinic(s, INF); } return flow;}void Topo(){ for(int i = 1; i <= n*m; i++) ban[i] = true; int head = 0, tail = 0; for(int i = 1; i <= n*m; i++) if(!in[i]) q[tail++] = i; tail --; while(head <= tail){ int now = q[head++]; ban[now] = false; for(EDGE *p = HEAD[now]; p; p = p->next){ int v = p->obj; in[v] --; if(!in[v]) q[++tail] = v; } }}int main(){ scanf("%d%d", &n, &m); s = 1; t = s + n * m + 1; for(int i = s; i <= t; i++) head_p[i] = -1, HEAD[i] = NULL; int w, x, y; for(int i = 1; i <= n*m; i++){ scanf("%d", &sco[i]); scanf("%d", &w); for(int j = 1; j <= w; j++){ scanf("%d%d", &x, &y); Insert(i, x*m+y+1); in[x*m+y+1] ++; } if(i % m){ Insert(i+1, i); in[i] ++; } } Topo(); for(int i = 1; i <= n*m; i++) for(EDGE *p = HEAD[i]; p; p = p->next){ int v = p->obj; if(!ban[i] && !ban[v]){ Addedge(s+v, s+i, INF); Addedge(s+i, s+v, 0); } } for(int i = 1; i <= n*m; i++){ if(ban[i]) continue; if(sco[i] > 0){ Addedge(s, s+i, sco[i]); Addedge(s+i, s, 0); SUM += sco[i]; } else{ Addedge(s+i, t, -sco[i]); Addedge(t, s+i, 0); } } printf("%d\n", SUM - MinCut()); return 0;}
花非花
我心如明镜
但睁眼亦庆幸
唯独我不可以发声
如顽强疾病与命格相衬
——《众生》
- 网络流水题五杀——洛谷 P3701、P2472、P1129、P2053、P2805
- 洛谷P2053:[SCOI2007]修车 (网络流)
- 二分图——洛谷P1129 [ZJOI2007]矩阵游戏
- 洛谷P3701:「伪模板」主席树 (网络流)
- 洛谷P1129 ZJOI2007矩阵游戏
- 洛谷P1129 [ZJOI2007]矩阵游戏
- 洛谷 P2805 [NOI2009 D2T1] 植物大战僵尸
- BZOJ1066 (洛谷P2472) [SCOI2007]蜥蜴
- [P2472]蜥蜴
- hdu3549-网络流水题
- 网络流水题--value
- STM32——流水灯
- P2472 [SCOI2007]蜥蜴
- HDU 3549 网络流水题
- 菜鸟学STM32——流水灯
- 51单片机——流水灯源代码
- ARM裸机——FS2410 流水灯
- 单片机AT89c51——流水灯设计
- error: .repo/manifests/: contains uncommitted changes
- 密码加密详解
- c 专家程序设计 运算符 i++
- webservice、socket和http 区别(一)
- Android夜间模式实现(系统自带)
- 网络流水题五杀——洛谷 P3701、P2472、P1129、P2053、P2805
- Android音视频-视频采集(系统API预览)
- 【Person Re-ID】常用评测指标
- MyBatis:SQL语句中的foreach的详细介绍
- Java面试学习总结(1)
- Python 小技巧:for 循环后面接 else 语句
- 刮刮卡
- 最优化问题——梯度下降/上升法
- Sublime Text3 3143 注册码,亲测可用!