Codeforces 最大流 费用流
这套题目做完后,一定要反复的看!
代码经常出现的几个问题:
本机测试超时:
1.init函数忘记写。
2.addedge函数写成add函数。
3.边连错了。
代码TLE:
1.前向星边数组开小.
2.用了memset,慎用。
1. CodeForces 498C Array and Operations
我发现cf上的网络流的建图思路都非常好,准备着重练习一下。
此题枚举每一个质因子,对每个质因子建图,确实是很好的思路。
1 #include <bits/stdc++.h> 2 using namespace std; 3 4 #define next Next 5 const int inf = 0x3f3f3f3f; 6 const int maxn=205; 7 int level[maxn]; 8 int iter[maxn]; 9 int head[maxn],tot; 10 struct edge{ 11 int to,cap,Next; 12 } e[5500]; ///此处应为边的两倍,加一条容量为0的反向边 13 void init(int n){ 14 15 for(int i = 0; i <= n; i++) head[i] = -1; 16 tot=0; 17 } 18 void add(int from,int to,int cap){ 19 e[tot].Next=head[from]; 20 e[tot].to=to; 21 e[tot].cap=cap; 22 head[from]=tot; 23 tot++; 24 } 25 void addedge(int from,int to,int cap){ 26 add(from,to,cap); 27 add(to,from,0); 28 } 29 void bfs(int s){ 30 memset(level,-1,sizeof(level)); 31 queue<int> q; 32 level[s]=0; 33 q.push(s); 34 while(!q.empty()){ 35 int v=q.front(); q.pop(); 36 for(int i=head[v];~i;i=e[i].Next){ 37 edge &ed=e[i]; 38 if(ed.cap>0&&level[ed.to]<0){ 39 level[ed.to]=level[v]+1; 40 q.push(ed.to); 41 } 42 } 43 } 44 } 45 int dfs(int v,int t,int f){ 46 if(v==t) return f; 47 for(int &i=iter[v];~i;i=e[i].Next){ 48 edge &ed=e[i]; 49 if(ed.cap>0&&level[v]<level[ed.to]){ 50 int d=dfs(ed.to,t,min(f,ed.cap)); 51 if(d>0){ 52 ed.cap-=d; 53 e[i^1].cap+=d; 54 return d; 55 } 56 } 57 } 58 return 0; 59 } 60 int max_flow(int s,int t){ 61 int flow=0; 62 while(1){ 63 bfs(s); 64 if(level[t]<0) return flow; 65 memcpy(iter,head,sizeof(iter)); 66 int f; 67 while((f=dfs(s,t,inf))>0){ 68 flow+=f; 69 } 70 } 71 } 72 73 //线性素数筛 74 const int N = 1e5 + 50; 75 int prime[N], num_prime = 0; 76 int isNotPrime[N]; 77 void is_prime(int N) 78 { 79 isNotPrime[0] = isNotPrime[1] = 1; 80 for(int i=2;i<N;i++) 81 { 82 if(!isNotPrime[i]) 83 { 84 prime[num_prime++] = i; 85 } 86 for(int j=0;j<num_prime&&i*prime[j]<N;j++) 87 { 88 isNotPrime[i*prime[j]] = 1; 89 if(!(i%prime[j])) 90 { 91 break; 92 } 93 } 94 } 95 return; 96 } 97 98 int a[111]; 99 vector<int> p[111]; 100 vector<int> cnt[111]; 101 int l[111][2]; 102 vector<int> big; ///大素数 103 int n, m; 104 int solve(int ans) 105 { 106 init(n + 1); 107 int s = 0, t = n + 1; 108 for(int j = 1; j <= n; j++) 109 { 110 int pos = lower_bound(p[j].begin(), p[j].end(), ans) - p[j].begin(); 111 if(pos < p[j].size() && p[j][pos] == ans) 112 { 113 // printf("%d %d %d\n", j, p[j][pos], cnt[j][pos]); 114 if(j & 1) addedge(s, j, cnt[j][pos]); 115 else addedge(j, t, cnt[j][pos]); 116 } 117 } 118 for(int j = 1; j <= m; j++) 119 { 120 int tmp = 100; 121 for(int k = 0; k < 2; k++) 122 { 123 // printf("%d\n", l[j][k]); 124 int pos = lower_bound(p[l[j][k]].begin(), p[l[j][k]].end(), ans) - p[l[j][k]].begin(); 125 // printf("%d %d %d\n", j, pos, cnt[l[j][k]][pos]); 126 if(pos < p[l[j][k]].size() && p[l[j][k]][pos] == ans) 127 { 128 tmp = min(tmp, cnt[l[j][k]][pos]); 129 } 130 else tmp = 0; 131 } 132 // printf("%d %d %d\n", l[j][0], l[j][1], tmp); 133 addedge(l[j][0], l[j][1], tmp); 134 } 135 // return 0; 136 return max_flow(s, t); 137 } 138 int main() 139 { 140 is_prime(1e5 + 5); 141 scanf("%d %d", &n, &m); 142 for(int i = 1; i <= n; i++) scanf("%d", &a[i]); 143 for(int i = 1; i <= n; i++) 144 { 145 for(int j = 2; j <= 1e5; j++) 146 { 147 if(j > a[i]) break; 148 if(a[i] % j == 0) p[i].push_back(j); 149 int tmp = 0; 150 while(a[i] % j == 0) 151 { 152 tmp++; 153 a[i] /= j; 154 } 155 if(tmp) cnt[i].push_back(tmp); 156 } 157 if(a[i] > 1) p[i].push_back(a[i]), cnt[i].push_back(1); 158 } 159 for(int i = 1; i <= m; i++) 160 { 161 int go1, go2; scanf("%d %d", &go1, &go2); 162 if(go1 % 2 == 0) swap(go1, go2); 163 l[i][0] = go1, l[i][1] = go2; 164 } 165 int ans = 0; 166 for(int i = 0; i < num_prime; i++) 167 { 168 ans += solve(prime[i]); 169 170 } 171 for(int i = 1; i <= n; i++) 172 { 173 int t = p[i].size(); 174 if(t == 0) continue; 175 if(p[i][t - 1] >= 1e5) big.push_back(p[i][t - 1]); 176 } 177 sort(big.begin(), big.end()); 178 big.erase(unique(big.begin(), big.end()), big.end()); 179 for(int i = 0; i < (int)big.size(); i++) 180 { 181 ans += solve(big[i]); 182 } 183 printf("%d\n", ans); 184 return 0; 185 } 186 /* 187 3 2 188 6 12 8 189 1 2 190 2 3 191 */
2. CodeForces 884FAnti-Palindromize
好图。
题目保证反回文串一定有解,就保证下面这样建图的正确性。
在26个字母和源$S$之间连<字母出现的次数,费用0>的边。 建$\frac{n}{2}$个新的结点,表示$i$到$n-i+1$的映射关系。从这$\frac{n}{2}$个节点向汇$T$连<容量为2,费用为0>的边。
然后对于每一个字母$c$,分三种情况连边:
如果字母$c$在结点$i$所对应的$i$到$n-i+1$中出现过两次,那么在$c$和$i$之间连一条<容量为1,费用为$max(b[i], b[n - i + 1]$>的边。(当然最后留下最大的一个)
如果出现过一次,那么在$c$和$i$之间连一条<容量为1,费用为那一次的$b$>的边。
如果没有出现过,在$c$和$i$之间连一条<容量为1,费用为0>的边。
#include <bits/stdc++.h> using namespace std; const int maxn = 200; const int INF = 1e9; int dist[maxn]; int pv[maxn],pe[maxn]; struct edge { int to, cap, rev; int cost; edge(int a, int b, int c, int d) { to = a, cap = b, cost = c, rev = d; } }; vector<edge> g[maxn]; void addedge(int from,int to,int cap,int cost) { g[from].push_back(edge(to,cap,cost,g[to].size())); g[to].push_back(edge(from,0,-cost,g[from].size()-1)); } int vis[maxn]; void SPFA(int s, int t) { for(int i = 1; i < maxn; i++) dist[i] = INF; memset(vis, 0, sizeof(vis)); dist[s] = 0, vis[s] = 1; queue<int> q; q.push(s); while(!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for(int i = 0; i < g[u].size(); i++) { edge &e = g[u][i]; if(e.cap > 0 && (dist[e.to] - (dist[u] + e.cost)) > 0) { pv[e.to] = u, pe[e.to] = i; dist[e.to] = dist[u] + e.cost; if(!vis[e.to]) { vis[e.to] = 1; q.push(e.to); } } } } } int min_cost_flow(int s,int t,int f,int& max_flow) { int ret = 0.0; while(f>0) { SPFA(s, t); if(dist[t] == INF) return ret;///同一目的地,每次增广路都是最小费用 ///当所有边的流量都流净后,即没有残余网络,返回。 int d = f; for(int v=t;v!=s;v=pv[v]) { d = min(d,g[pv[v]][pe[v]].cap); } f -= d; max_flow += d; ret += (int)d*dist[t]; ///走一单位就消耗dist[t] for(int v=t;v!=s;v=pv[v]) { edge &e = g[pv[v]][pe[v]]; e.cap -= d; g[v][e.rev].cap += d; } } return ret; } char s[111]; int b[111]; int cnt[30]; int main() { int n; scanf("%d", &n); scanf("%s", s + 1); for(int i = 1; i <= n; i++) scanf("%d", &b[i]); for(int i = 1; i <= n; i++) { cnt[s[i] - 'a' + 1]++; } int S = 26 + n / 2 + 1, T = S + 1; for(int i = 1; i <= 26; i++) { addedge(S, i, cnt[i], 0); // printf("%c %d\n", i + 'a' - 1, cnt[i]); if(cnt[i]) { for(int j = 1; j <= n / 2; j++) { char c = i + 'a' - 1; int flag = 0; if(s[j] == c) flag++; if(s[n - j + 1] == c) flag++; if(!flag) addedge(i, 26 + j, 1, 0); else if(flag == 1) { if(s[j] == c) addedge(i, 26 + j, 1, -b[j]); else addedge(i, 26 + j, 1, -b[n - j + 1]); } else { addedge(i, 26 + j, 1, min(-b[j], -b[n - j + 1])); } } } } for(int j = 1; j <= n / 2; j++) { addedge(j + 26, T, 2, 0); } int maxflow = 0; int ans = min_cost_flow(S, T, INF, maxflow); printf("%d\n", -ans); return 0; }
3. CodeForces 164C Machine Programming
算了好几次head大小愣是算不对,这题前面出过好几次了。 焦作F
这个输出路径还是用前向星好,每条边都有索引,方便判断$e.cap$是否用完。
#include <bits/stdc++.h> using namespace std; const int maxn = 3000; const int INF = 1e9; int dist[maxn]; int pv[maxn],pe[maxn]; struct edge { int to, cap, pre; int cost; }e[7000]; int tot = 0, head[maxn]; void init(int n) { tot = 0; fill(head, head + n + 1, -1); } void add(int from,int to,int cap,double cost) { e[tot].pre = head[from]; e[tot].to = to; e[tot].cap = cap; e[tot].cost = cost; head[from] = tot++; } void addedge(int from,int to,int cap,double cost) { add(from,to,cap,cost); add(to,from,0,-cost); } int vis[maxn]; void SPFA(int s, int t) { for(int i = 0; i < maxn; i++) dist[i] = INF; memset(vis, 0, sizeof(vis)); dist[s] = 0, vis[s] = 1; queue<int> q; q.push(s); while(!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for(int i = head[u]; ~i; i = e[i].pre) { int to = e[i].to, cap = e[i].cap; if(cap > 0 && (dist[to] - (dist[u] + e[i].cost)) > 0) { pv[to] = u, pe[to] = i; dist[to] = dist[u] + e[i].cost; if(!vis[to]) { vis[to] = 1; q.push(to); } } } } } int min_cost_flow(int s,int t,int f,int& max_flow) { int ret = 0.0; while(f>0) { SPFA(s, t); if(dist[t] == INF) return ret;///同一目的地,每次增广路都是最小费用 ///当所有边的流量都流净后,即没有残余网络,返回。 int d = f; for(int v=t;v!=s;v=pv[v]) { d = min(d, e[pe[v]].cap); } f -= d; max_flow += d; ret += (int)d*dist[t]; ///走一单位就消耗dist[t] for(int v=t;v!=s;v=pv[v]) { e[pe[v]].cap -= d; e[pe[v]^1].cap += d; } } return ret; } int s[1111], t[1111], c[1111], x[2222]; int main() { init(2900); int n, k; scanf("%d %d", &n, &k); int cnt = 0; for(int i = 1; i <= n; i++) { scanf("%d %d %d", &s[i], &t[i], &c[i]); t[i] = s[i] + t[i]; x[cnt++] = s[i], x[cnt++] = t[i]; } sort(x, x + cnt); cnt = unique(x,x + cnt) - x; for(int i = 1; i <= n; i++) { s[i] = lower_bound(x, x + cnt, s[i]) - x; t[i] = lower_bound(x, x + cnt, t[i]) - x; } int S = cnt + 2, T = S + 3; for(int i = 1; i <= n; i++) { addedge(s[i], t[i], 1, -c[i]); } addedge(S, 0, k, 0); for(int i = 0; i < cnt; i++) { addedge(i, i + 1, k, 0); } addedge(cnt, T, k, 0); int maxflow = 0; min_cost_flow(S, T, INF, maxflow); for(int i = 0; i < 2 * n; i += 2) { if(e[i].cap == 0) printf("1 "); else printf("0 "); } return 0; }
4. Gym 100212I Trade
太蠢了,这种构图竟然都想不到……
每个点度数$≥2$,那我只要在源点$S$和$u$之间连一条$deg[u]-2$的边,汇点那边做同样的处理。
这样如果这条边能去掉,就自然的从$S$到$T$流过来了。
然后看正向边有没有流完,就可以去掉了。
#include <bits/stdc++.h> using namespace std; #define next Next const int inf = 0x3f3f3f3f; const int maxn=705; int level[maxn]; int iter[maxn]; int head[maxn],tot; struct edge{ int to,cap,Next; } e[200000]; ///此处应为边的两倍,加一条容量为0的反向边 void init(int n){ fill(head, head + n + 1, -1); tot=0; } void add(int from,int to,int cap){ e[tot].Next=head[from]; e[tot].to=to; e[tot].cap=cap; head[from]=tot; tot++; } void addedge(int from,int to,int cap){ add(from,to,cap); add(to,from,0); } void bfs(int s){ memset(level,-1,sizeof(level)); queue<int> q; level[s]=0; q.push(s); while(!q.empty()){ int v=q.front(); q.pop(); for(int i=head[v];~i;i=e[i].Next){ edge &ed=e[i]; if(ed.cap>0&&level[ed.to]<0){ level[ed.to]=level[v]+1; q.push(ed.to); } } } } int dfs(int v,int t,int f){ if(v==t) return f; for(int &i=iter[v];~i;i=e[i].Next){ edge &ed=e[i]; if(ed.cap>0&&level[v]<level[ed.to]){ int d=dfs(ed.to,t,min(f,ed.cap)); if(d>0){ ed.cap-=d; e[i^1].cap+=d; return d; } } } return 0; } int max_flow(int s,int t){ int flow=0; while(1){ bfs(s); if(level[t]<0) return flow; memcpy(iter,head,sizeof(iter)); int f; while((f=dfs(s,t,inf))>0){ flow+=f; } } } int u[90005], v[90005]; int deu[305], dev[300]; int del[90005]; int main() { freopen("trade.in", "r", stdin); freopen("trade.out", "w", stdout); init(701); int n, m, k; scanf("%d %d %d", &n, &m, &k); for(int i = 1; i <= k; i++) { scanf("%d %d", &u[i], &v[i]); deu[u[i]]++, dev[v[i]]++; } for(int i = 1; i <= n; i++) { if(deu[i] < 2) { printf("-1\n"); return 0; } } for(int i = 1; i <= m; i++) { if(dev[i] < 2) { printf("-1\n"); return 0; } } for(int i = 1; i <= k; i++) { addedge(u[i], v[i] + n, 1); } int S = n + m + 1, T = n + m + 2; for(int i = 1; i <= n; i++) { if(deu[i] >= 2) addedge(S, i, deu[i] - 2); } for(int i = 1; i <= m; i++) { if(dev[i] >= 2) addedge(i + n, T, dev[i] - 2); } int ans = max_flow(S, T); printf("%d\n", k - ans); for(int i = 0; i < 2 * k; i += 2) { if(e[i].cap == 0) del[i / 2 + 1] = 1; } for(int i = 1; i <= k; i++) { if(!del[i]) printf("%d ", i); } return 0; }
5. CodeForces 730I Olympiad in Programming and Sports
怎么保证每个点只被用一次?
从p和s连向它,再引出一条流量为1的边。
#include <bits/stdc++.h> using namespace std; const int maxn = 3100; const int INF = 1e9; int dist[maxn]; int pv[maxn],pe[maxn]; struct edge { int to, cap, pre; int cost; }e[20000]; int tot = 0, head[maxn]; void init(int n) { tot = 0; fill(head, head + n + 1, -1); } void add(int from,int to,int cap,double cost) { e[tot].pre = head[from]; e[tot].to = to; e[tot].cap = cap; e[tot].cost = cost; head[from] = tot++; } void addedge(int from,int to,int cap,double cost) { add(from,to,cap,cost); add(to,from,0,-cost); } int vis[maxn]; void SPFA(int s, int t) { for(int i = 1; i < maxn; i++) dist[i] = INF; memset(vis, 0, sizeof(vis)); dist[s] = 0, vis[s] = 1; queue<int> q; q.push(s); while(!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for(int i = head[u]; ~i; i = e[i].pre) { int to = e[i].to, cap = e[i].cap; if(cap > 0 && (dist[to] - (dist[u] + e[i].cost)) > 0) { pv[to] = u, pe[to] = i; dist[to] = dist[u] + e[i].cost; if(!vis[to]) { vis[to] = 1; q.push(to); } } } } } int min_cost_flow(int s,int t,int f,int& max_flow) { int ret = 0; while(f>0) { SPFA(s, t); if(dist[t] == INF) return ret;///同一目的地,每次增广路都是最小费用 ///当所有边的流量都流净后,即没有残余网络,返回。 int d = f; for(int v=t;v!=s;v=pv[v]) { d = min(d, e[pe[v]].cap); } f -= d; max_flow += d; ret += (int)d*dist[t]; ///走一单位就消耗dist[t] for(int v=t;v!=s;v=pv[v]) { e[pe[v]].cap -= d; e[pe[v]^1].cap += d; } } return ret; } int main() { int n, p, s; scanf("%d %d %d", &n, &p, &s); init(n + 4); int l = n + 1, r = n + 2, S = n + 3, T = S + 1; for(int i = 1; i <= n; i++) { int a; scanf("%d", &a); addedge(l, i, 1, -a); } for(int i = 1; i <= n; i++) { int b; scanf("%d", &b); addedge(r, i, 1, -b); } addedge(S, l, p, 0), addedge(S, r, s, 0); for(int i = 1; i <= n; i++) { addedge(i, T, 1, 0); } int maxflow = 0; int ans = min_cost_flow(S, T, INF, maxflow); printf("%d\n", -ans); for(int i = 0; i < 2 * n; i += 2) { if(e[i].cap == 0) printf("%d ", i / 2 + 1); } printf("\n"); for(int i = 2 * n; i < 4 * n; i += 2) { if(e[i].cap == 0) printf("%d ", i / 2 - n + 1); } }
6. Gym 101755D Transfer Window
很开心,接连几道题自己可以独立建图,并且成功AC,说明没有白训练,做 -> 受益 -> 做,形成闭环。
这道题图不是很难建,但是要输出网络流的路径,这就要求对最大流模板很熟悉,改模板。
#include <bits/stdc++.h> using namespace std; #define next Next const int inf = 0x3f3f3f3f; const int maxn=400; int level[maxn]; int iter[maxn]; int head[maxn],tot; int pv[maxn],pe[maxn]; struct edge{ int to,cap,Next; } e[200000]; ///此处应为边的两倍,加一条容量为0的反向边 void init(int n){ fill(head, head + n + 1, -1); tot=0; } void add(int from,int to,int cap){ e[tot].Next=head[from]; e[tot].to=to; e[tot].cap=cap; head[from]=tot; tot++; } void addedge(int from,int to,int cap){ add(from,to,cap); add(to,from,0); } void bfs(int s){ memset(level,-1,sizeof(level)); queue<int> q; level[s]=0; q.push(s); while(!q.empty()){ int v=q.front(); q.pop(); for(int i=head[v];~i;i=e[i].Next){ edge &ed=e[i]; if(ed.cap>0&&level[ed.to]<0){ level[ed.to]=level[v]+1; q.push(ed.to); } } } } int dfs(int v,int t,int f){ if(v==t) return f; for(int &i=iter[v];~i;i=e[i].Next){ edge &ed=e[i]; if(ed.cap>0&&level[v]<level[ed.to]){ int d=dfs(ed.to,t,min(f,ed.cap)); if(d>0){ pv[ed.to] = v, pe[ed.to] = i; ed.cap-=d; e[i^1].cap+=d; return d; } } } return 0; } vector<int> out[maxn]; int max_flow(int s,int t){ int flow=0; while(1){ bfs(s); if(level[t]<0) return flow; memcpy(iter,head,sizeof(iter)); int f; while((f=dfs(s,t,inf))>0) { int last = 0; for(int v = t; v != s; v = pv[v]) { last = v; } for(int v = pv[t]; v != s; v = pv[v]) { out[last].push_back(v); } flow+=f; } } } char s[maxn][maxn]; struct node { int l, r; }cur[90005]; int bel[305]; int vis[90005]; int main() { int n, k; scanf("%d %d", &n, &k); init(n + 2); int S = n + 1, T = S + 1; for(int i = 1; i <= k; i++) { int a; scanf("%d", &a); addedge(S, a, 1); bel[a]++; } for(int i = 1; i <= k; i++) { int a; scanf("%d", &a); addedge(a, T, 1); } for(int i = 1; i <= n; i++) { scanf("%s", s[i] + 1); for(int j = 1; j <= n; j++) { if(s[i][j] == '1') { addedge(i, j, inf); } } } int ans = max_flow(S, T); if(ans < k) { printf("NO\n"); return 0; } int num = 0; for(int i = 1; i <= n; i++) { if(out[i].size() > 1) { for(int j = out[i].size() - 1; j > 0; j--) { num++; cur[num].l = out[i][j], cur[num].r = out[i][j - 1]; } } } if(num > n * n) { printf("NO\n"); return 0; } printf("YES\n"); printf("%d\n", num); while(1) { int flag = 0; for(int i = 1; i <= num; i++) { if(vis[i]) continue; int l = cur[i].l, r = cur[i].r; if(bel[l] == 1 && bel[r] == 0) { printf("%d %d\n", l, r); flag = 1; vis[i] = 1; bel[l] = 0, bel[r] = 1; } } if(!flag) { break; } } return 0; } /* 6 3 1 2 4 4 5 6 000100 000100 000000 000010 000001 000000 */
7. CodeForces 847J Students Initiation
当出现一条边,要么朝向左,要么朝向右,这时候不妨把它从中间拎起来,让源从这个顶点流。
当出现一个点,要么表现为1属性,要么表现为2属性,不妨把这两个属性想成一条边的两个点,把这条边中间往下沉,让这个顶点往汇流。
此题属于第一种情况。
#include <bits/stdc++.h> using namespace std; #define next Next const int inf = 0x3f3f3f3f; const int maxn=11000; int level[maxn]; int iter[maxn]; int head[maxn],tot; struct edge{ int to,cap,Next; } e[50000]; ///此处应为边的两倍,加一条容量为0的反向边 void init(int n){ fill(head, head + n + 1, -1); tot=0; } void add(int from,int to,int cap){ e[tot].Next=head[from]; e[tot].to=to; e[tot].cap=cap; head[from]=tot; tot++; } void addedge(int from,int to,int cap){ add(from,to,cap); add(to,from,0); } void bfs(int s){ memset(level,-1,sizeof(level)); queue<int> q; level[s]=0; q.push(s); while(!q.empty()){ int v=q.front(); q.pop(); for(int i=head[v];~i;i=e[i].Next){ edge &ed=e[i]; if(ed.cap>0&&level[ed.to]<0){ level[ed.to]=level[v]+1; q.push(ed.to); } } } } int dfs(int v,int t,int f){ if(v==t) return f; for(int &i=iter[v];~i;i=e[i].Next){ edge &ed=e[i]; if(ed.cap>0&&level[v]<level[ed.to]){ int d=dfs(ed.to,t,min(f,ed.cap)); if(d>0){ ed.cap-=d; e[i^1].cap+=d; return d; } } } return 0; } int max_flow(int s,int t){ int flow=0; while(1){ bfs(s); if(level[t]<0) return flow; memcpy(iter,head,sizeof(iter)); int f; while((f=dfs(s,t,inf))>0) { flow+=f; } } } int x[5005], y[5005]; int n, m; bool check(int mid) { init(n + m + 2); int S = m + n + 1, T = S + 1; for(int i = 1; i <= m; i++) //每个i是边 { addedge(i, x[i] + m, 1); addedge(i, y[i] + m, 1); } for(int i = 1; i <= m; i++) { addedge(S, i, 1); } for(int i = 1; i <= n; i++) { addedge(i + m, T, mid); } return max_flow(S, T) == m; } struct node { int x, y; }cur[5005]; int main() { scanf("%d %d", &n, &m); for(int i = 1; i <= m; i++) scanf("%d %d", &x[i], &y[i]); int l = 1, r = m; int ans = 0; while(l <= r) { int mid = (l + r) >> 1; if(check(mid)) { ans = mid; for(int i = 0; i < 4 * m; i += 4) { int tmp = i / 4 + 1; // printf("%d %d\n", e[i].cap, e[i + 2].cap); if(e[i].cap == 0) cur[tmp].x = e[i].to - m, cur[tmp].y = e[i + 2].to - m; else cur[tmp].x = e[i + 2].to - m, cur[tmp].y = e[i].to - m; } r = mid - 1; } else l = mid + 1; } if(ans <= 0) return 0 * puts("0"); printf("%d\n", ans); for(int i = 1; i <= m; i++) { printf("%d %d\n", cur[i].x, cur[i].y); } return 0; }
8. CodeForces 316C2 Tidying Up
实际上我觉得这题算是二分图最小权匹配。
还是先把矩阵染成黑白相间,就是$i+j$为偶数染成白色,否则染成黑色。
每个白点向周围的黑色区域连边,相同的鞋连0,不同的鞋连1。此图满足二分图的两个性质:“0元素”(白色集合内之间不会有边)和“1元素”(白色集合中的点和黑色集合中的一个点最多只会有一条边)。
我们对题目仔细分析就会发现,实际上,同样的鞋,我们最多只会移动其中一只去找另一只,而不会都动,如果都动这样只会使答案变大。
跑二分图最小权匹配后,就是匹配边中边权为0的边越多越好。匹配边中边权为0的边,就说明这条边所连的两只鞋,是同一双鞋,都不用动了。
剩下匹配边中边权为1的边,总共$n$条这样的边的话,所连的$2*n$只鞋,我们只需要移动其中的$n$只鞋去找相同的鞋就可以了。就是在这$n$只鞋所在的点中来回换。
#include <bits/stdc++.h> using namespace std; const int maxn=7000; const int INF = 1e9; int dist[maxn]; int pv[maxn],pe[maxn]; struct edge { int to, cap, pre; int cost; }e[40000]; int tot = 0, head[maxn]; void init(int n) { tot = 0; fill(head, head + n + 1, -1); } void add(int from,int to,int cap,int cost) { e[tot].pre = head[from]; e[tot].to = to; e[tot].cap = cap; e[tot].cost = cost; head[from] = tot++; } void addedge(int from,int to,int cap,int cost) { // printf("%d ", cost); add(from,to,cap,cost); add(to,from,0,-cost); } int vis[maxn]; void SPFA(int s, int t) { for(int i = 1; i < maxn; i++) dist[i] = INF; memset(vis, 0, sizeof(vis)); dist[s] = 0, vis[s] = 1; queue<int> q; q.push(s); while(!q.empty()) { int u = q.front(); q.pop(); vis[u] = 0; for(int i = head[u]; ~i; i = e[i].pre) { int to = e[i].to, cap = e[i].cap; if(cap > 0 && (dist[to] - (dist[u] + e[i].cost)) > 0) { pv[to] = u, pe[to] = i; dist[to] = dist[u] + e[i].cost; if(!vis[to]) { vis[to] = 1; q.push(to); } } } } } int min_cost_flow(int s,int t,int f,int& max_flow) { int ret = 0.0; while(f>0) { SPFA(s, t); if(dist[t] == INF) return ret;///同一目的地,每次增广路都是最小费用 ///当所有边的流量都流净后,即没有残余网络,返回。 int d = f; for(int v=t;v!=s;v=pv[v]) { d = min(d, e[pe[v]].cap); } f -= d; max_flow += d; ret += (int)d*dist[t]; ///走一单位就消耗dist[t] for(int v=t;v!=s;v=pv[v]) { e[pe[v]].cap -= d; e[pe[v]^1].cap += d; } } return ret; } int shoes[88][88]; int n, m; int check(int x, int y) { if(x >= 1 && x <= n && y >= 1 && y <= m) return 1; else return 0; } int main() { scanf("%d %d", &n, &m); init(n * m + 5); for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { scanf("%d", &shoes[i][j]); } } int S = n * m + 1, T = S + 1; for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) { // printf("%d %d\n", i, j); int node = (i - 1) * m + j; if((i + j) % 2 == 0) { addedge(S, node, 1, 0); if(check(i - 1, j)) addedge(node, node - m, 1, shoes[i][j] != shoes[i - 1][j]); if(check(i + 1, j)) addedge(node, node + m, 1, shoes[i][j] != shoes[i + 1][j]); if(check(i, j - 1)) addedge(node, node - 1, 1, shoes[i][j] != shoes[i][j - 1]); if(check(i, j + 1)) addedge(node, node + 1, 1, shoes[i][j] != shoes[i][j + 1]); } else addedge(node, T, 1, 0); // printf("\n"); } } int maxflow = 0; printf("%d\n", min_cost_flow(S, T, INF, maxflow)); return 0; }
9. CodeForces 1070I I. Privatization of Roads in Berland
因为最近网络流做的比较多,比赛时候一心找网络流,做完前两道题后,发现I题是网络流,(感觉已经熟悉了cf的网络流题目风格)想先做完C再去做,结果C题WA 31全程,暴露对拍了200组n≤1000的数组都没差异,泪奔……
自己独立建图,搞了一个多小时还是不知道怎么建,看了题解后,发现这种度数-限制的题目前面做过(Gym 100212I Trade)这两天要把前面做过的题目再看一遍,感觉都是很好的模型。
题解:
首先每个公司的两条边和同一个城市相连是最优的。因为如果不连同一个城市的话,一个公司两条边连接四个不同的城市,每个城市都被占了$\frac{1}{k}$的名额,还不如两条边连同一个城市,两条边才占了一个城市$\frac{1}{k}$的名额。
假如一个公司的度数为$d$,如果$d≤k$,那我们不用担心。如果$d>2\times k$,构造不出。如果$k<d≤2\times k$,那么至少有$d-k$条边与$k$条边中相同,就是至少会有$d-k$对相同的边。
那么我们可以这样建图:每条边变成一个顶点,$S$向每条边连流量为1的边,每条边分别向两个端点$u$和$v$连流量为1的边,对于每个端点,向$T$连$2\times (max(0, d-k))$流量的边,就相当于对于每个城市,去构造$d-k$对相同的边。跑最大流就可以了。
#include <bits/stdc++.h> using namespace std; const int inf = 0x3f3f3f3f; const int maxn=1300; int level[maxn]; int iter[maxn]; int head[maxn],tot; struct edge{ int to,cap,Next; } e[5000]; ///此处应为边的两倍,加一条容量为0的反向边 void init(int n) { fill(head, head + n + 1, -1); tot=0; } void add(int from,int to,int cap){ e[tot].Next=head[from]; e[tot].to=to; e[tot].cap=cap; head[from]=tot; tot++; } void addedge(int from,int to,int cap){ add(from,to,cap); add(to,from,0); } void bfs(int s){ memset(level,-1,sizeof(level)); queue<int> q; level[s]=0; q.push(s); while(!q.empty()){ int v=q.front(); q.pop(); for(int i=head[v];~i;i=e[i].Next){ edge &ed=e[i]; if(ed.cap>0&&level[ed.to]<0){ level[ed.to]=level[v]+1; q.push(ed.to); } } } } int dfs(int v,int t,int f){ if(v==t) return f; for(int &i=iter[v];~i;i=e[i].Next){ edge &ed=e[i]; if(ed.cap>0&&level[v]<level[ed.to]){ int d=dfs(ed.to,t,min(f,ed.cap)); if(d>0){ ed.cap-=d; e[i^1].cap+=d; return d; } } } return 0; } int max_flow(int s,int t){ int flow=0; while(1){ bfs(s); if(level[t]<0) return flow; memcpy(iter,head,sizeof(iter)); int f; while((f=dfs(s,t,inf))>0){ flow+=f; } } } vector<int> cnt[700]; int ans[700]; int deg[700]; int main() { int T; scanf("%d", &T); while(T--) { int n, m, k; scanf("%d %d %d", &n, &m, &k); init(n + m + 5); fill(ans, ans + m + 1, 0); fill(deg, deg + n + 1, 0); for(int i = 1; i <= n; i++) cnt[i].clear(); for(int i = 1; i <= m; i++) { int u, v; scanf("%d %d", &u, &v); addedge(i, u + m, 1); addedge(i, v + m, 1); deg[u]++, deg[v]++; } int S = n + m + 1, T = S + 1; for(int i = 1; i <= m; i++) addedge(S, i, 1); int num = 0; for(int i = 1; i <= n; i++) { if(deg[i] > k) { addedge(i + m, T, 2 * (deg[i] - k)); num += deg[i] - k; } } num <<= 1; if(num > max_flow(S, T)) { for(int i = 1; i <= m; i++) printf("0 "); printf("\n"); continue; } for(int i = 0; i < 4 * m; i += 4) { if(e[i].cap == 0) cnt[e[i].to - m].push_back(i / 4 + 1); else if(e[i + 2].cap == 0)cnt[e[i + 2].to - m].push_back(i / 4 + 1); } int flag = 0; for(int i = 1; i <= n; i++) { for(int j = 0; j < cnt[i].size(); j += 2) { flag++; ans[cnt[i][j]] = flag; ans[cnt[i][j + 1]] = flag; } } for(int i = 1; i <= m; i++) { if(ans[i]) printf("%d ", ans[i]); else printf("%d ", ++flag); } printf("\n"); } return 0; }
10 Gym 101190D Delight for a Cat
做到这题,我发现我必须总结一下这种题型了。
Gym 101736D Dessert First Strategy