网络流专题
POJ 1149 PIGS
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 10005, INF = 1e9; struct Edge{ int to, nxt, cap; }e[N << 1]; int head[N], cur[N], dis[N], q[N], En = 1, S, T; void add_edge(int u,int v,int w) { // cout << u << " " << v << " " << w << "\n"; ++En; e[En].to = v; e[En].nxt = head[u]; e[En].cap = w; head[u] = En; ++En; e[En].to = u; e[En].nxt = head[v]; e[En].cap = 0; head[v] = En; } bool bfs() { for (int i = 0; i <= T; ++i) dis[i] = -1, cur[i] = head[i]; int L = 1, R = 0; q[++R] = S; dis[S] = 0; while (L <= R) { int u = q[L ++]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == -1 && e[i].cap > 0) { dis[v] = dis[u] + 1; q[++R] = v; if (v == T) return 1; } } } return 0; } int dfs(int u,int flow) { if (u == T) return flow; int used = 0, t; for (int &i = cur[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == dis[u] + 1 && e[i].cap > 0) { t = dfs(v, min(e[i].cap, flow - used)); if (t > 0) { used += t, e[i].cap -= t, e[i ^ 1].cap += t; if (used == flow) break; } } } if (used != flow) dis[u] = -1; return used; } int dinic() { int ans = 0; while (bfs()) ans += dfs(S, INF); return ans; } vector<int> vec[N]; int w[N], cnt[N]; int main() { //freopen("1.txt", "r", stdin); int m = read(), n = read(); for (int i = 1; i <= m; ++i) w[i] = read(); for (int i = 1; i <= n; ++i) { int t = read(); while (t--) { int u = read(); vec[u].push_back(i); } cnt[i] = read(); } S = 0; T = n + 1; for (int i = 1; i <= n; ++i) add_edge(i, T, cnt[i]); for (int i = 1; i <= m; ++i) { if (vec[i].size() == 0) continue; add_edge(S, vec[i][0], w[i]); for (int j = 1; j < vec[i].size(); ++j) { add_edge(vec[i][j - 1], vec[i][j], INF); } } cout << dinic(); return 0; }
POJ 2699 The Maximum Number of Strong Kings
分析:枚举strong king的个数,然后网络流判断是否可行。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 2005, INF = 1e9; struct Edge{ int to, nxt, cap; }e[N << 1]; int head[N], cur[N], dis[N], q[N], En = 1, S, T; void add_edge(int u,int v,int w) { // cout << u << " " << v << " " << w << "\n"; ++En; e[En].to = v; e[En].nxt = head[u]; e[En].cap = w; head[u] = En; ++En; e[En].to = u; e[En].nxt = head[v]; e[En].cap = 0; head[v] = En; } bool bfs() { for (int i = 0; i <= T; ++i) dis[i] = -1, cur[i] = head[i]; int L = 1, R = 0; q[++R] = S; dis[S] = 0; while (L <= R) { int u = q[L ++]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == -1 && e[i].cap > 0) { dis[v] = dis[u] + 1; q[++R] = v; if (v == T) return 1; } } } return 0; } int dfs(int u,int flow) { if (u == T) return flow; int used = 0, t; for (int &i = cur[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == dis[u] + 1 && e[i].cap > 0) { t = dfs(v, min(e[i].cap, flow - used)); if (t > 0) { used += t, e[i].cap -= t, e[i ^ 1].cap += t; if (used == flow) break; } } } if (used != flow) dis[u] = -1; return used; } int dinic() { int ans = 0; while (bfs()) ans += dfs(S, INF); return ans; } int n, tot, a[N], id[N][N]; bool solve(int k) { En = 1; memset(head, 0, sizeof(head)); S = 0; T = n + tot + 1; for (int i = 1; i <= n; ++i) add_edge(S, i, a[i]); for (int i = n + 1; i <= n + tot; ++i) add_edge(i, T, 1); for (int i = 1; i <= n; ++i) { for (int j = i + 1; j <= n; ++j) { if (i >= n - k + 1 && a[i] < a[j]) add_edge(i, id[i][j] + n, 1); else add_edge(i, id[i][j] + n, 1), add_edge(j, id[i][j] + n, 1); } } return dinic() == tot; } void solve() { n = tot = 0; string s; getline(cin, s); int now = 0; // for (int i = 0; i < (int)s.size(); ++i) { // if (s[i] == ' ') a[++n] = now, now = 0; // else now = now * 10 + s[i] - '0'; // } // a[++n] = now; for (int i = 0; i < (int)s.size(); ++i) { // 注意此处的读入!!!不要向上面一样读入!!! if (isdigit(s[i])) { now = 0; while (i < s.size() && isdigit(s[i])) { now = now * 10 + (s[i] - '0'); i ++; } i --; a[++n] = now; } } for (int i = 1; i <= n; ++i) for (int j = i + 1; j <= n; ++j) id[i][j] = id[j][i] = ++tot; for (int i = n; i >= 1; --i) if (solve(i)) { printf("%d\n", i); return ; } puts("0"); } int main() { int T = read(); for (; T--; ) solve(); return 0; }
ZOJ 2760 How Many Shortest Path
分析:求出那些边可以存在于最短路上,然后跑网络流。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 2005, INF = 1e9; struct Edge{ int to, nxt, cap; }e[30005]; int head[N], cur[N], dis[N], q[N], En = 1, S, T, n; void add_edge(int u,int v,int w) { // printf("%d %d %d\n", u, v, w); ++En; e[En].to = v; e[En].nxt = head[u]; e[En].cap = w; head[u] = En; ++En; e[En].to = u; e[En].nxt = head[v]; e[En].cap = 0; head[v] = En; } bool bfs() { for (int i = 1; i <= n; ++i) dis[i] = -1, cur[i] = head[i]; int L = 1, R = 0; q[++R] = S; dis[S] = 0; while (L <= R) { int u = q[L ++]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == -1 && e[i].cap > 0) { dis[v] = dis[u] + 1; q[++R] = v; if (v == T) return 1; } } } return 0; } int dfs(int u,int flow) { if (u == T) return flow; int used = 0, t; for (int &i = cur[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == dis[u] + 1 && e[i].cap > 0) { t = dfs(v, min(e[i].cap, flow - used)); if (t > 0) { used += t, e[i].cap -= t, e[i ^ 1].cap += t; if (used == flow) break; } } } if (used != flow) dis[u] = -1; return used; } int dinic() { int ans = 0; while (bfs()) ans += dfs(S, INF); return ans; } int g[N][N], t[N][N]; void solve() { En = 1; memset(head, 0, sizeof(head)); if (S == T) { puts("inf"); return ; } for (int k = 1; k <= n; ++k) for (int i = 1;i <= n; ++i) for (int j = 1; j <= n; ++j) if (g[i][k] != -1 && g[k][j] != -1 && (g[i][k] + g[k][j] < g[i][j] || g[i][j] == -1)) g[i][j] = g[i][k] + g[k][j]; if (g[S][T] == -1) { puts("0"); return ; } for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) if (i != j && g[S][T] == g[S][i] + t[i][j] + g[j][T] && g[S][i] != -1 && g[j][T] != -1) add_edge(i, j, 1); printf("%d\n", dinic()); } int main() { // freopen("1.txt", "r", stdin); while (~scanf("%d", &n)) { for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) t[i][j] = g[i][j] = read(); for (int i = 1; i <= n; ++i) t[i][i] = g[i][i] = 0; S = read() + 1, T = read() + 1; solve(); } return 0; }
WOJ 124 Football match
题意:
有 N 支球队,互相之间已经进行了一些比赛,还剩下M场没有比。现在给出各 支球队目前的总分以及还剩下哪 M 场没有比,问能否合理安排这 M 场比赛的结 果,使得第 N 支球队最后的总分大于其他任何一支球队的总分。已知每场比赛 胜者得 2 分,败者 0 分,平局则各得 1 分。(1 <= N <= 100, 0 <= M <= 1000)
分析:首先让所有n参见的比赛,让n赢,然后剩下的建图跑最大流,判断是否可行。S向每个比赛连,流量为2,每个比赛向两个球队连,流量为2,每个球队向T连,流量为w[n]-w[i]-1,表示最大的得分。
#include<cstdio> #include<algorithm> #include<cstring> #include<cmath> #include<iostream> #include<cctype> #include<set> #include<vector> #include<queue> #include<map> #define fi(s) freopen(s,"r",stdin); #define fo(s) freopen(s,"w",stdout); using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 10005, INF = 1e9; struct Edge{ int to, nxt, cap; }e[N << 1]; int head[N], cur[N], dis[N], q[N], En = 1, S, T; void add_edge(int u,int v,int w) { // printf("%d %d %d\n", u, v, w); ++En; e[En].to = v; e[En].nxt = head[u]; e[En].cap = w; head[u] = En; ++En; e[En].to = u; e[En].nxt = head[v]; e[En].cap = 0; head[v] = En; } bool bfs() { for (int i = 0; i <= T; ++i) dis[i] = -1, cur[i] = head[i]; int L = 1, R = 0; q[++R] = S; dis[S] = 0; while (L <= R) { int u = q[L ++]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == -1 && e[i].cap > 0) { dis[v] = dis[u] + 1; q[++R] = v; if (v == T) return 1; } } } return 0; } int dfs(int u,int flow) { if (u == T) return flow; int used = 0, t; for (int &i = cur[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == dis[u] + 1 && e[i].cap > 0) { t = dfs(v, min(e[i].cap, flow - used)); if (t > 0) { used += t, e[i].cap -= t, e[i ^ 1].cap += t; if (used == flow) break; } } } if (used != flow) dis[u] = -1; return used; } int dinic() { int ans = 0; while (bfs()) ans += dfs(S, INF); return ans; } int n, m, a[N], b[N], w[N]; void solve() { En = 1; memset(head, 0, sizeof(head)); int tot = 0, now = 0; S = 0; for (int i = 1; i <= m; ++i) { if (a[i] == n || b[i] == n) w[n] += 2, a[i] = b[i] = 0; else { now ++; add_edge(S, now, 2); } } // cout << "match" << now << "\n"; for (int i = 1; i <= n; ++i) if (w[i] > w[n]) { puts("NO"); return ; } tot = now; now = 0; for (int i = 1; i <= m; ++i) { if (a[i] && b[i]) { now ++; add_edge(now, a[i] + tot, 2); add_edge(now, b[i] + tot, 2); } } T = tot + n + 1; for (int i = 1; i < n; ++i) add_edge(i + tot, T, w[n] - w[i] - 1); puts(dinic() == tot * 2 ? "YES" : "NO"); } int main() { // freopen("1.txt", "r", stdin); while (~scanf("%d%d", &n, &m)) { for (int i = 1; i <= n; ++i) w[i] = read(); for (int i = 1; i <= m; ++i) a[i] = read(), b[i] = read(); solve(); } return 0; }
SGU 438. The Glorious Karlutka River =)
题意:一条宽为W的河,河中有N块石头,每块石头的坐标(Xi, Yi)和每一时刻最大承受人数Ci。有M个游客,每次最远跳D米,每跳一次1秒。问能否所有人穿越这条河流,如果能,最少需要多长时间。 N,M<=50
分析:动态流问题,首先可以通过判断是否联通来得知能否过河(或者利用最差清空的时间是n+m来判断)。然后按时间拆点,建图跑网络流。每个石头拆成两个点,然后对于一条边u,v,从上一秒的u到下一秒的v连一条inf的边,每秒源点也要连出边。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> #define pa pair<int,int> #define mp make_pair using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 100005, INF = 1e9; struct Edge{ int to, nxt, cap; }e[N << 1]; int head[N], cur[N], dis[N], q[N], En = 1, S, T, Index; void add_edge(int u,int v,int w) { // printf("%d %d %d\n", u, v, w); ++En; e[En].to = v; e[En].nxt = head[u]; e[En].cap = w; head[u] = En; ++En; e[En].to = u; e[En].nxt = head[v]; e[En].cap = 0; head[v] = En; } bool bfs() { for (int i = 0; i <= Index; ++i) dis[i] = -1, cur[i] = head[i]; int L = 1, R = 0; q[++R] = S; dis[S] = 0; while (L <= R) { int u = q[L ++]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == -1 && e[i].cap > 0) { dis[v] = dis[u] + 1; q[++R] = v; if (v == T) return 1; } } } return 0; } int dfs(int u,int flow) { if (u == T) return flow; int used = 0, t; for (int &i = cur[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == dis[u] + 1 && e[i].cap > 0) { t = dfs(v, min(e[i].cap, flow - used)); if (t > 0) { used += t, e[i].cap -= t, e[i ^ 1].cap += t; if (used == flow) break; } } } if (used != flow) dis[u] = -1; return used; } int dinic() { int ans = 0; while (bfs()) ans += dfs(S, INF); return ans; } int n, m, D, L, x[N], y[N], w[N], fa[N], chu[N], ru[N]; vector< pa > g; vector<int> p; int sqr(int x) { return x * x; } int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); } bool pd() { for (int i = 1; i <= n + 2; ++i) fa[i] = i; for (int i = 1; i <= n; ++i) { if (!w[i]) continue; if (y[i] <= D) { int r1 = find(n + 1), r2 = find(i); if (r1 != r2) fa[r2] = r1; g.push_back(mp(0, i)); p.push_back(i); } if (y[i] >= L - D) { int r1 = find(n + 2), r2 = find(i); if (r1 != r2) fa[r2] = r1; p.push_back(i); g.push_back(mp(i, n + 1)); } } for (int i = 1; i <= n; ++i) for (int j = i + 1; j <= n; ++j) { if (!w[i] || !w[j] || i == j) continue; if (sqr(x[i] - x[j]) + sqr(y[i] - y[j]) > D * D) continue; int r1 = find(i), r2 = find(j); if (r1 != r2) fa[r2] = r1; p.push_back(i), p.push_back(j); g.push_back(mp(i, j)); } p.push_back(0), p.push_back(n + 1); sort(p.begin(), p.end()); p.erase(unique(p.begin(), p.end()), p.end()); return find(n + 1) != find(n + 2); } int main() { n = read(), m = read(), D = read(), L = read(); if (D >= L) { puts("1"); return 0; } // D >= L not D > L !!!!! for (int i = 1; i <= n; ++i) { x[i] = read(), y[i] = read(), w[i] = read(); } if (pd()) { puts("IMPOSSIBLE"); return 0; } S = 0, T = n + n + 1; Index = T; for (int i = 1; i <= n; ++i) { ru[i] = i, chu[i] = i + n; if (y[i] <= D) add_edge(S, i, INF), add_edge(i, i + n, w[i]); } chu[S] = ru[S] = S, chu[n + 1] = ru[n + 1] = T; w[T] = INF;int flow = 0; for (int Ti = 1; Ti <= n + m; ++Ti) { flow += dinic(); if (flow >= m) { cout << Ti; return 0; } for (int i = 1; i < (int)p.size() - 1; ++i) ru[p[i]] = ++Index; for (int i = 0; i < (int)g.size(); ++i) { int u = g[i].first, v = g[i].second; if (p[v] != S && chu[u] != T) add_edge(chu[u], ru[v], INF); if (p[u] != S && chu[v] != T) add_edge(chu[v], ru[u], INF); } for (int i = 1; i < (int)p.size() - 1; ++i) { chu[p[i]] = ++Index; add_edge(ru[p[i]], chu[p[i]], w[p[i]]); } } return 0; }
SPOJ NETADMIN - Smart Network Administrator
分析: 二分一个mid,然后给每条边设置容量为mid,从源点向每个想要链接internet的用户连一条容量为1的边,从1向汇点连一条容量为k的边。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 100005, INF = 1e9; struct Edge{ int to, nxt, cap; }e[N << 1]; int head[N], cur[N], dis[N], q[N], En = 1, S, T; void add_edge(int u,int v,int w) { ++En; e[En].to = v; e[En].nxt = head[u]; e[En].cap = w; head[u] = En; ++En; e[En].to = u; e[En].nxt = head[v]; e[En].cap = 0; head[v] = En; } bool bfs() { for (int i = 0; i <= T; ++i) dis[i] = -1, cur[i] = head[i]; int L = 1, R = 0; q[++R] = S; dis[S] = 0; while (L <= R) { int u = q[L ++]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == -1 && e[i].cap > 0) { dis[v] = dis[u] + 1; q[++R] = v; if (v == T) return 1; } } } return 0; } int dfs(int u,int flow) { if (u == T) return flow; int used = 0, t; for (int &i = cur[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == dis[u] + 1 && e[i].cap > 0) { t = dfs(v, min(e[i].cap, flow - used)); if (t > 0) { used += t, e[i].cap -= t, e[i ^ 1].cap += t; if (used == flow) break; } } } if (used != flow) dis[u] = -1; return used; } int dinic() { int ans = 0; while (bfs()) ans += dfs(S, INF); return ans; } #define pa pair<int,int> vector< pa > g; int n, m, k; int h[N]; bool check(int c) { En = 1; memset(head, 0, sizeof(head)); S = 0, T = n + 1; for (int i = 1; i <= k; ++i) add_edge(S, h[i], 1); add_edge(1, T, k); for (int i = 0; i < (int)g.size(); ++i) { add_edge(g[i].first, g[i].second, c); add_edge(g[i].second, g[i].first, c); } return dinic() == k; } void solve() { g.clear(); n = read(), m = read(), k = read(); for (int i = 1; i <= k; ++i) h[i] = read(); for (int i = 1; i <= m; ++i) g.push_back(pa(read(), read())); int L = 1, R = k, ans = 0; while (L <= R) { int mid = (L + R) >> 1; if (check(mid)) ans = mid, R = mid - 1; else L = mid + 1; } cout << ans << "\n"; } int main() { freopen("1.txt", "r", stdin); for (int T = read(); T --; ) solve(); return 0; }
SPOJ IM - Intergalactic Map
题意:无向图中,能否从1走到2,再从2走到3,每个点只经过一次。
分析:源点向2连,容量为2,1和3向汇点连,容量为1,建图跑网络流,判断容量是否为2。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 200005, INF = 1e9; struct Edge{ int to, nxt, cap; }e[N << 1]; int head[N], cur[N], dis[N], q[N], En = 1, S, T; void add_edge(int u,int v,int w) { ++En; e[En].to = v; e[En].nxt = head[u]; e[En].cap = w; head[u] = En; ++En; e[En].to = u; e[En].nxt = head[v]; e[En].cap = 0; head[v] = En; } bool bfs() { for (int i = 0; i <= T; ++i) dis[i] = -1, cur[i] = head[i]; int L = 1, R = 0; q[++R] = S; dis[S] = 0; while (L <= R) { int u = q[L ++]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == -1 && e[i].cap > 0) { dis[v] = dis[u] + 1; q[++R] = v; if (v == T) return 1; } } } return 0; } int dfs(int u,int flow) { if (u == T) return flow; int used = 0, t; for (int &i = cur[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == dis[u] + 1 && e[i].cap > 0) { t = dfs(v, min(e[i].cap, flow - used)); if (t > 0) { used += t, e[i].cap -= t, e[i ^ 1].cap += t; if (used == flow) break; } } } if (used != flow) dis[u] = -1; return used; } int dinic() { int ans = 0; while (bfs()) ans += dfs(S, INF); return ans; } void solve() { En = 1; memset(head, 0, sizeof(head)); int n = read(), m = read(); S = 0, T = n + n + 1; for (int i = 1; i <= n; ++i) add_edge(i, i + n, 1); for (int i = 1; i <= m; ++i) { int u = read(), v = read(); if (u < 1 || v < 1 || u > n || v > n) continue; // 注意!!! add_edge(u + n, v, 1); add_edge(v + n, u, 1); } add_edge(S, 2 + n, 2); add_edge(1 + n, T, 1); add_edge(3 + n, T, 1); puts(dinic() == 2 ? "YES" : "NO"); } int main() { for (int T = read(); T --; ) solve(); return 0; }
POJ 1637 Sightseeing tour
题意:混合图欧拉回路的判定。
分析:首先给无向图随便定向,然后判断每个点的初度-入度有没有奇数,如有有,无解。否则S向出>入的点连,容量为(出-入)/2,入>出的点向T连,容量为(入-出)/2,原图中的无向边保留,有向边删掉,跑最大流,有解的条件是满流。
理解:每个的点度数之差如果是奇数,无论怎么改变无向边的方向,这个点的度数之差始终是奇数,所以无解;否则可以尝试着修改一些无向边的方向,使得每个点的出度等于入度。那么在网络中,如果一个点x的出度>入度,那么说明这个点需要改变一些出边变成入边,来满足条件,改变的条数是c=(出-入)/2;如果点y的入度>出度,那么就是改变入边,条数d=(入-出)/2。S向x连一条容量为c的边,y向T连一条容量为d的边。网络流中的一条增广路径经过的边,就是需要反转的边,如果满流,说明存在解。详细的解释证明
代码:
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 3005, INF = 1e9; struct Edge{ int to, nxt, cap; }e[N << 1]; int head[N], cur[N], dis[N], q[N], En = 1, S, T; void add_edge(int u,int v,int w) { ++En; e[En].to = v; e[En].nxt = head[u]; e[En].cap = w; head[u] = En; ++En; e[En].to = u; e[En].nxt = head[v]; e[En].cap = 0; head[v] = En; } bool bfs() { for (int i = 0; i <= T; ++i) dis[i] = -1, cur[i] = head[i]; int L = 1, R = 0; q[++R] = S; dis[S] = 0; while (L <= R) { int u = q[L ++]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == -1 && e[i].cap > 0) { dis[v] = dis[u] + 1; q[++R] = v; if (v == T) return 1; } } } return 0; } int dfs(int u,int flow) { if (u == T) return flow; int used = 0, t; for (int &i = cur[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == dis[u] + 1 && e[i].cap > 0) { t = dfs(v, min(e[i].cap, flow - used)); if (t > 0) { used += t, e[i].cap -= t, e[i ^ 1].cap += t; if (used == flow) break; } } } if (used != flow) dis[u] = -1; return used; } int dinic() { int ans = 0; while (bfs()) ans += dfs(S, INF); return ans; } int x[N], y[N], d[N], ru[N], chu[N]; void init() { En = 1; memset(head, 0, sizeof(head)); memset(ru, 0, sizeof(ru)); memset(chu, 0, sizeof(chu)); } void solve() { init(); int n = read(), m = read(), sum = 0; for (int i = 1; i <= m; ++i) { x[i] = read(), y[i] = read(), d[i] = read(); ru[y[i]] ++, chu[x[i]] ++; } for (int i = 1; i <= n; ++i) { if (abs(ru[i] - chu[i]) & 1) { puts("impossible"); return ; } } S = 0, T = n + 1; for (int i = 1; i <= m; ++i) { if (!d[i]) add_edge(x[i], y[i], 1); } for (int i = 1; i <= n; ++i) { if (chu[i] == ru[i]) continue; if (ru[i] > chu[i]) add_edge(i, T, (ru[i] - chu[i]) / 2); if (chu[i] > ru[i]) add_edge(S, i, (chu[i] - ru[i]) / 2), sum += (chu[i] - ru[i]) / 2; } puts(dinic() == sum ? "possible" : "impossible"); } int main() { for (int T = read(); T -- ; ) solve(); return 0; }
2756: [SCOI2012]奇怪的游戏
分析:黑白染色,讨论,网络流判断。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 2005; const LL INF = 1e16; struct Edge{ int to, nxt;LL cap; }e[50005]; int head[N], cur[N], dis[N], q[N], En = 1, S, T, n, m; void add_edge(int u,int v,LL w) { // printf("%d %d %I64d\n", u, v, w); ++En; e[En].to = v; e[En].nxt = head[u]; e[En].cap = w; head[u] = En; ++En; e[En].to = u; e[En].nxt = head[v]; e[En].cap = 0; head[v] = En; } bool bfs() { for (int i = 0; i <= T; ++i) dis[i] = -1, cur[i] = head[i]; int L = 1, R = 0; q[++R] = S; dis[S] = 0; while (L <= R) { int u = q[L ++]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == -1 && e[i].cap > 0) { dis[v] = dis[u] + 1; q[++R] = v; if (v == T) return 1; } } } return 0; } LL dfs(int u,LL flow) { if (u == T) return flow; LL used = 0, t; for (int &i = cur[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == dis[u] + 1 && e[i].cap > 0) { t = dfs(v, min(e[i].cap, flow - used)); if (t > 0) { used += t, e[i].cap -= t, e[i ^ 1].cap += t; if (used == flow) break; } } } if (used != flow) dis[u] = -1; return used; } LL dinic() { LL ans = 0; while (bfs()) ans += dfs(S, INF); return ans; } LL a[50][50]; int id[50][50]; bool check(LL k) { LL t = 0; for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) { if ((i + j) & 1) add_edge(id[i][j], T, k - a[i][j]); else { add_edge(S, id[i][j], k - a[i][j]); t += k - a[i][j]; if (i > 1) add_edge(id[i][j], id[i - 1][j], INF); if (i < n) add_edge(id[i][j], id[i + 1][j], INF); if (j > 1) add_edge(id[i][j], id[i][j - 1], INF); if (j < m) add_edge(id[i][j], id[i][j + 1], INF); } } t = (dinic() == t); En = 1; for (int i = 0; i <= T; ++i) head[i] = 0; return t; } void solve() { n = read(), m = read(); LL Mx = 0; for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) a[i][j] = read(), Mx = max(Mx, a[i][j]); LL sum0 = 0, sum1 = 0, cnt0 = 0, cnt1 = 0; for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m; ++j) { if ((i + j) & 1) sum1 += a[i][j], cnt1 ++; else sum0 += a[i][j], cnt0 ++; } } if (cnt0 == cnt1 && sum0 != sum1) { puts("-1"); return ; } S = 0, T = n * m + 1; for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) id[i][j] = (i - 1) * m + j; if (cnt0 == cnt1) { LL L = Mx, R = INF, ans = 0; while (L <= R) { LL mid = (L + R) >> 1; if (check(mid)) ans = mid, R = mid - 1; else L = mid + 1; } printf("%lld\n", (ans * cnt0 - sum0)); } else { LL k = (sum0 - sum1) / (cnt0 - cnt1); if (k >= Mx && check(k)) printf("%lld\n", (k * cnt0 - sum0)); else puts("-1"); } } int main() { for (int T = read(); T --; ) solve(); return 0; }
UVA 11082 Matrix Decompressing
分析:经典问题,行列拆成一个点,S->行点,容量为行的和,列点->T,容量为列的和,中间两两连边,注意一下,原题中不能有0,权值范围为[1,20],因为所有边的容量一样,可以同时减去1(注意S->行,列->T的边也要减),然后求出后统一+1。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 2005, INF = 1e9; struct Edge{ int to, nxt, cap; }e[N << 1]; int head[N], cur[N], dis[N], q[N], En = 1, S, T; void add_edge(int u,int v,int w) { ++En; e[En].to = v; e[En].nxt = head[u]; e[En].cap = w; head[u] = En; ++En; e[En].to = u; e[En].nxt = head[v]; e[En].cap = 0; head[v] = En; } bool bfs() { for (int i = 0; i <= T; ++i) dis[i] = -1, cur[i] = head[i]; int L = 1, R = 0; q[++R] = S; dis[S] = 0; while (L <= R) { int u = q[L ++]; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == -1 && e[i].cap > 0) { dis[v] = dis[u] + 1; q[++R] = v; if (v == T) return 1; } } } return 0; } int dfs(int u,int flow) { if (u == T) return flow; int used = 0, t; for (int &i = cur[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] == dis[u] + 1 && e[i].cap > 0) { t = dfs(v, min(e[i].cap, flow - used)); if (t > 0) { used += t, e[i].cap -= t, e[i ^ 1].cap += t; if (used == flow) break; } } } if (used != flow) dis[u] = -1; return used; } int dinic() { int ans = 0; while (bfs()) ans += dfs(S, INF); return ans; } int a[N], b[N], id[N][N]; void solve() { int n = read(), m = read(); S = 0, T = n + m + 1; for (int i = 1; i <= n; ++i) { a[i] = read(); add_edge(S, i, a[i] - a[i - 1] - m); } for (int i = 1; i <= m; ++i) { b[i] = read(); add_edge(i + n, T, b[i] - b[i - 1] - n); } for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) add_edge(i, j + n, 19), id[i][j] = En; dinic(); for (int i = 1; i <= n; ++i, puts("")) for (int j = 1; j <= m; ++j) printf("%d ", 1 + e[id[i][j]].cap); puts(""); En = 1; for (int i = 0; i <= T; ++i) head[i] = 0; } int main() { for (int T = read(), i = 1; i <= T; ++i) { printf("Matrix %d\n", i); solve(); } return 0; }
1927: [Sdoi2010]星际竞速
分析:首先注意到这是一个DAG,每个点要保证只经过一次,可以从任意点出发。类似一个哈密顿回路(如果把瞬移也加入到图中的话)。考虑一个点是从哪里来的,可以从任意点直接瞬移到这里,可以从连向这个点的点走到这里。
1、那么由于从任意点瞬移到这个点的花费是一定的,所以可以直接从S->i+n(这些点设为i+n)连边,花费为瞬移的时间,容量为1。
2、从连向这个点的位置走过来,花费为路径的权值,由于这是一个类似哈密顿路径的东西,每个点也只能出去一次,所以再建一列点(i),表示从每个点出去,S->i,容量为1(表示这个点只能出去一次),花费为0;对于一条边u->v,连边u->v+n,容量为1,花费为边权。
3、u+n->T连边,容量为1,花费为0。u+n表示已经到过u这个点了,容量为1,表示只能由一个点到这个点(哈密顿路径中,要求出度入度都为1)。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> #define pa pair<int,int> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 2005, INF = 1e9; struct Edge{ int fr, to, nxt, cap, cost; } e[50005]; int head[N], dis[N], pre[N], En = 1, S, T, q[100005]; bool vis[N]; inline void add_edge(int u,int v,int f,int w) { ++En; e[En].fr = u, e[En].to = v, e[En].nxt = head[u], e[En].cap = f, e[En].cost = w; head[u] = En; ++En; e[En].fr = v, e[En].to = u, e[En].nxt = head[v], e[En].cap = 0, e[En].cost = -w; head[v] = En; } bool spfa() { for (int i=1; i<=T; ++i) dis[i] = INF, vis[i] = false, pre[i] = 0; int L = 1, R = 0; q[++R] = S, dis[S] = 0, vis[S] = true, pre[S] = 0; while (L <= R) { int u = q[L ++]; vis[u] = false; for (int i=head[u]; i; i=e[i].nxt) { int v = e[i].to; if (dis[v] > dis[u] + e[i].cost && e[i].cap > 0) { dis[v] = dis[u] + e[i].cost; pre[v] = i; if (!vis[v]) q[++R] = v, vis[v] = true; } } } return dis[T] != INF; } void mcf() { LL Flow = 0, Cost = 0; while (spfa()) { int now = INF; for (int i = T; i != S; i = e[pre[i]].fr) now = min(now, e[pre[i]].cap); for (int i = T; i != S; i = e[pre[i]].fr) e[pre[i]].cap -= now, e[pre[i] ^ 1].cap += now; Cost += 1ll * now * dis[T]; Flow += now; } cout << Cost; } int main() { int n = read(), m = read(); S = 0, T = n + n + 1; for (int i = 1; i <= n; ++i) { int x = read(); add_edge(S, i + n, 1, x); add_edge(S, i, 1, 0); add_edge(i + n, T, 1, 0); } for (int i = 1; i <= m; ++i) { int u = read(), v = read(), w = read(); if (u > v) swap(u, v); add_edge(u, v + n, 1, w); } mcf(); return 0; }
2324: [ZJOI2011]营救皮卡丘
分析:费用流,建模。首先转化为有向图无环图(DAG)的最小权路径覆盖问题,预处理出g[i][j],表示i->j只经过编号小于j或者i的点,最短路。然后就是一张n*n的竞赛图了(有向),选小于等于k条路径,覆盖所有点,花费最小。S->0,容量为k,花费为0,S-1~n,容量为1,花费为0;对于u->v,连接u->v+n,容量为1,花费为g[i][j];连接i+n->T,容量为1,花费为0。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 505, INF = 1e9; int g[N][N]; struct Edge{ int fr, to, nxt, cap, cost; }e[500005]; int head[N], dis[N], pre[N], q[100005], En = 1, S, T; bool vis[N]; inline void add_edge(int u,int v,int f,int w) { ++En; e[En].fr = u, e[En].to = v, e[En].nxt = head[u], e[En].cap = f, e[En].cost = w; head[u] = En; ++En; e[En].fr = v, e[En].to = u, e[En].nxt = head[v], e[En].cap = 0, e[En].cost = -w; head[v] = En; } bool spfa() { for (int i = 0; i <= T; ++i) dis[i] = INF, vis[i] = false, pre[i] = 0; int L = 1, R = 0; dis[S] = 0, q[++R] = S, vis[S] = true, pre[S] = 0; while (L <= R) { int u = q[L ++]; vis[u] = false; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] > dis[u] + e[i].cost && e[i].cap > 0) { dis[v] = dis[u] + e[i].cost; pre[v] = i; if (!vis[v]) q[++R] = v, vis[v] = true; } } } return dis[T] != INF; } int mcf() { int Flow = 0, Cost = 0; while (spfa()) { int now = INF; for (int i = T; i != S; i = e[pre[i]].fr) now = min(now, e[pre[i]].cap); for (int i = T; i != S; i = e[pre[i]].fr) e[pre[i]].cap -= now, e[pre[i] ^ 1].cap += now; Flow += now; Cost += now * dis[T]; } return Cost; } int main() { int n = read(), m = read(), k = read(); memset(g, 0x3f, sizeof(g)); for (int i = 1; i <= n; ++i) g[i][i] = 0; for (int i = 1; i <= m; ++i) { int u = read(), v = read(), w = read(); g[u][v] = g[v][u] = min(g[u][v], w); } for (int k = 0; k <= n; ++k) for (int i = 0; i <= n; ++i) for (int j = 0; j <= n; ++j) if (k <= i || k <= j) g[i][j] = min(g[i][j], g[i][k] + g[k][j]); S = n + n + 1, T = S + 1; add_edge(S, 0, k, 0); for (int i = 1; i <= n; ++i) add_edge(S, i, 1, 0), add_edge(i + n, T, 1, 0); for (int i = 0; i <= n; ++i) for (int j = i + 1; j <= n; ++j) add_edge(i, j + n, 1, g[i][j]); printf("%d\n", mcf()); return 0; }
CF 277 E. Binary Tree on Plane
分析:每个点拆成两个点,一个表示作为父节点(i),另一个表示作为子节点(i+n)。然后S向i连边,容量为2(表示只能选两个子节点),花费为0;i+n向T连边容量为1,花费为0;如果i可以作为j的父节点,那么i向j+n连边,容量为1,花费为i与j之间的距离。
如果存在解,那么跑完最小费用最大流后,流量要求大于等于n-1。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 500005, INF = 1e9; const double DINF = 1e18; struct Edge{ int fr, to, nxt, cap;double cost; }e[N << 1]; int head[N], pre[N], q[N], En = 1, S, T, n; double dis[N]; bool vis[N]; inline void add_edge(int u,int v,int f,double w) { ++En; e[En].fr = u, e[En].to = v, e[En].nxt = head[u], e[En].cap = f, e[En].cost = w; head[u] = En; ++En; e[En].fr = v, e[En].to = u, e[En].nxt = head[v], e[En].cap = 0, e[En].cost = -w; head[v] = En; } bool spfa() { for (int i = 0; i <= T; ++i) dis[i] = DINF, vis[i] = false, pre[i] = 0; int L = 1, R = 0; dis[S] = 0, q[++R] = S, vis[S] = true, pre[S] = 0; while (L <= R) { int u = q[L ++]; vis[u] = false; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] > dis[u] + e[i].cost && e[i].cap > 0) { dis[v] = dis[u] + e[i].cost; pre[v] = i; if (!vis[v]) q[++R] = v, vis[v] = true; } } } return dis[T] != DINF; } void mcf() { int Flow = 0; double Cost = 0; while (spfa()) { int now = INF; for (int i = T; i != S; i = e[pre[i]].fr) now = min(now, e[pre[i]].cap); for (int i = T; i != S; i = e[pre[i]].fr) e[pre[i]].cap -= now, e[pre[i] ^ 1].cap += now; Flow += now; Cost += 1.0 * now * dis[T]; } if (Flow >= n - 1) printf("%.10lf", Cost); else puts("-1"); } struct Node{ int x, y; } A[N]; int sqr(int x) { return x * x; } double dist(const Node &A, const Node &B) { return sqrt(1.0 * (sqr(A.x - B.x) + sqr(A.y - B.y))); } int main() { n = read(); for (int i = 1; i <= n; ++i) A[i].x = read(), A[i].y = read(); S = 0, T = n + n + 1; for (int i = 1; i <= n; ++i) add_edge(S, i, 2, 0), add_edge(i + n, T, 1, 0); for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) if (A[j].y > A[i].y) add_edge(j, i + n, 1, dist(A[i], A[j])); mcf(); return 0; }
2879: [Noi2012]美食节
分析:费用提前计算。考虑第i分菜被第j个厨师做,这是他的第k份,一共做了t份,那么这份菜的花费就是(t-k+1)*T[i][j]。由于t是未知的,这样建图还是不太行,换种方法:考虑这个厨师倒数第k份菜是那个,那么它的花费就是k*T[i][j]。所以可以这样建图:每个厨师拆成sum_p个点,S向每个点连边,容量为1费用为0;每个点向每份菜连边,容量为1费用为T[i][j];每份菜向T连边,容量为p[i]费用为0。这样的话,边太多了,考虑优化,动态加边,每次增广会找到一条从S->厨师(倒数第i时刻的第j个厨师)->菜->T,因为时刻越靠前花费越多,所以一定会先找大的时刻,所以可以一开始加出所有的倒数第一个时刻的厨师,然后每次增广了一条路,找到这个厨师,把这个厨师的下一个时刻建出。
// luogu-judger-enable-o2 #include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 500005, INF = 1e9; struct Edge{ int fr, to, nxt, cap, cost; }e[N << 1]; int head[N], dis[N], pre[N], q[N], cnt[N], id[N], p[N], t[100][200], En = 1, S, T, n, m, Sum, Index; bool vis[N]; inline void add_edge(int u,int v,int f,int w) { ++En; e[En].fr = u, e[En].to = v, e[En].nxt = head[u], e[En].cap = f, e[En].cost = w; head[u] = En; ++En; e[En].fr = v, e[En].to = u, e[En].nxt = head[v], e[En].cap = 0, e[En].cost = -w; head[v] = En; } bool spfa() { for (int i = 0; i <= Index; ++i) dis[i] = INF, vis[i] = false, pre[i] = 0; int L = 1, R = 0; dis[S] = 0, q[++R] = S, vis[S] = true, pre[S] = 0; while (L <= R) { int u = q[L ++]; vis[u] = false; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] > dis[u] + e[i].cost && e[i].cap > 0) { dis[v] = dis[u] + e[i].cost; pre[v] = i; if (!vis[v]) q[++R] = v, vis[v] = true; // vis[v]=true!!! } } } return dis[T] != INF; } void mcf() { int Flow = 0, Cost = 0; while (spfa()) { int now = INF, x; for (int i = T; i != S; i = e[pre[i]].fr) { now = min(now, e[pre[i]].cap); if (e[pre[i]].fr == S) x = e[pre[i]].to; } for (int i = T; i != S; i = e[pre[i]].fr) e[pre[i]].cap -= now, e[pre[i] ^ 1].cap += now; Flow += now; Cost += now * dis[T]; if (Flow == Sum) { cout << Cost; return ; } x = id[x]; cnt[x] ++; if (cnt[x] <= Sum) { ++Index; id[Index] = x; add_edge(S, Index, 1, 0); for (int j = 1; j <= n; ++j) add_edge(Index, j, 1, t[j][x] * cnt[x]); } } } int main() { n = read(), m = read(); for (int i = 1; i <= n; ++i) p[i] = read(), Sum += p[i]; for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) t[i][j] = read(); S = 0, T = m + n + 1; for (int i = 1; i <= n; ++i) add_edge(i, T, p[i], 0); for (int i = 1; i <= m; ++i) add_edge(S, i + n, 1, 0), id[i + n] = i, cnt[i] = 1; for (int i = 1; i <= m; ++i) for (int j = 1; j <= n; ++j) add_edge(i + n, j, 1, t[j][i]); Index = T; mcf(); return 0; }
HDU 3667 Transportation
分析:每条边差分后拆成5条,分别是a,3a,5a,7a,9a。然后最小费用最大流。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 105, INF = 1e9; struct Edge{ int fr, to, nxt, cap, cost; }e[60005]; int head[N], dis[N], pre[N], q[100005], En = 1, S, T, n, m, k; bool vis[N]; inline void add_edge(int u,int v,int f,int w) { ++En; e[En].fr = u, e[En].to = v, e[En].nxt = head[u], e[En].cap = f, e[En].cost = w; head[u] = En; ++En; e[En].fr = v, e[En].to = u, e[En].nxt = head[v], e[En].cap = 0, e[En].cost = -w; head[v] = En; } bool spfa() { for (int i = 0; i <= T; ++i) dis[i] = INF, vis[i] = false, pre[i] = 0; int L = 1, R = 0; dis[S] = 0, q[++R] = S, vis[S] = true, pre[S] = 0; while (L <= R) { int u = q[L ++]; vis[u] = false; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] > dis[u] + e[i].cost && e[i].cap > 0) { dis[v] = dis[u] + e[i].cost; pre[v] = i; if (!vis[v]) q[++R] = v, vis[v] = true; } } } return dis[T] != INF; } void mcf() { int Flow = 0, Cost = 0; while (spfa()) { int now = INF; for (int i = T; i != S; i = e[pre[i]].fr) now = min(now, e[pre[i]].cap); for (int i = T; i != S; i = e[pre[i]].fr) e[pre[i]].cap -= now, e[pre[i] ^ 1].cap += now; Flow += now; Cost += now * dis[T]; } if (Flow != k) puts("-1"); else printf("%d\n", Cost); } void solve(){ S = 0, T = n; add_edge(S, 1, k, 0); for (int i = 1; i <= m; ++i) { int u = read(), v = read(), a = read(), c = read(); for (int j = 1; j <= c; ++j) add_edge(u, v, 1, (j * 2 - 1) * a); } mcf(); En = 1; for (int i = 0; i <= T; ++i) head[i] = 0; } int main() { while (~scanf("%d%d%d", &n, &m, &k)) solve(); return 0; }
UVA 12092 Paint the Roads
分析:每个点存在于k个环中,转化为每个点的出度,入度都等于k。然后拆成两列点,S->i,容量为k花费为0;i->j+n,容量为1花费为边的长度;i+n->T,容量为k花费为0。最小费用最大流。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 105, INF = 1e9; struct Edge{ int fr, to, nxt, cap, cost; }e[10005]; int head[N], dis[N], pre[N], q[10005], En = 1, S, T, n, m, k, Sum; bool vis[N]; inline void add_edge(int u,int v,int f,int w) { ++En; e[En].fr = u, e[En].to = v, e[En].nxt = head[u], e[En].cap = f, e[En].cost = w; head[u] = En; ++En; e[En].fr = v, e[En].to = u, e[En].nxt = head[v], e[En].cap = 0, e[En].cost = -w; head[v] = En; } bool spfa() { for (int i = 0; i <= T; ++i) dis[i] = INF, vis[i] = false, pre[i] = 0; int L = 1, R = 0; dis[S] = 0, q[++R] = S, vis[S] = true, pre[S] = 0; while (L <= R) { int u = q[L ++]; vis[u] = false; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] > dis[u] + e[i].cost && e[i].cap > 0) { dis[v] = dis[u] + e[i].cost; pre[v] = i; if (!vis[v]) q[++R] = v, vis[v] = true; } } } return dis[T] != INF; } void mcf() { int Flow = 0, Cost = 0; while (spfa()) { int now = INF; for (int i = T; i != S; i = e[pre[i]].fr) now = min(now, e[pre[i]].cap); for (int i = T; i != S; i = e[pre[i]].fr) e[pre[i]].cap -= now, e[pre[i] ^ 1].cap += now; Flow += now; Cost += now * dis[T]; } if (Flow == Sum) printf("%d\n", Cost); else puts("-1"); } void solve() { n = read(), m = read(), k = read(); S = 0, T = n + n + 1; Sum = n * k; for (int i = 1; i <= n; ++i) add_edge(S, i, k, 0), add_edge(i + n, T, k, 0); for (int i = 1; i <= m; ++i) { int u = read() + 1, v = read() + 1, d = read(); add_edge(u, v + n, 1, d); } mcf(); En = 1; for (int i = 0; i <= T; ++i) head[i] = 0; } int main() { for (int T = read(); T--; ) solve(); return 0; }
POJ 2175 Evacuation Plan
题意:n个建筑物,每个建筑物里有ai个人,m个避难所,每个避难所可以容纳bi个人,现在给出一个分配方案(g[i][j]表示第i个建筑物去第j个避难所的人数),问是否总的距离是否是最小的,如果不是,输出任意一组比当前优秀的解。
分析:消圈定理:所谓消圈定理,就是在某个流f中,如果其对应的残余网络没有负圈(剩余流量为0的边视为不存在),那它一定就是当前流量下的最小费用流。反之亦然。即「f是最小费用流等价于其残余网络中没有负圈」。by sengxian。那么在这道题中,建出残余网络,找一下负圈,如果存在,那么是说明存在更优的方案,然后在圈上增广一下,得到更优的方案。否则,不存在更优的方案。
#include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<cmath> #include<cctype> #include<set> #include<queue> #include<vector> #include<map> using namespace std; typedef long long LL; inline int read() { int x=0,f=1;char ch=getchar();for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1; for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';return x*f; } const int N = 205, INF = 1e9; struct Edge{ int to, nxt, cap, cost; }e[100005]; struct Node{ int x, y, z; } A[N], B[N]; int g[N][N], cnt[N], head[N], dis[N], pre[N], deg[N], En = 1, S, T, n, m; bool vis[N]; queue<int> q; inline void add_edge(int u,int v,int f,int w) { ++En; e[En].to = v, e[En].nxt = head[u], e[En].cap = f, e[En].cost = w; head[u] = En; } int spfa() { for (int i = 0; i <= T; ++i) dis[i] = INF, vis[i] = false, deg[i] = pre[i] = 0; q.push(S); dis[S] = 0, vis[S] = true, deg[S] ++; while (!q.empty()) { int u = q.front(); q.pop(), vis[u] = false; for (int i = head[u]; i; i = e[i].nxt) { int v = e[i].to; if (dis[v] > dis[u] + e[i].cost && e[i].cap > 0) { dis[v] = dis[u] + e[i].cost; pre[v] = u; if (!vis[v]) { q.push(v), vis[v] = true; if ((++deg[v]) > T) return v; } } } } return -1; } void solve(int x) { for (int i = 0; i <= T; ++i) vis[i] = 0; while (!vis[x]) vis[x] = 1, x = pre[x]; // 这里的中间是逗号,写成了分号。。 int z = x, y; do { y = pre[x]; if (y <= n && x > n) g[y][x - n] ++; if (y > n && x <= n) g[x][y - n] --; x = pre[x]; } while (x != z); } int main() { n = read(), m = read(); S = 0, T = n + m + 1; for (int i = 1; i <= n; ++i) A[i].x = read(), A[i].y = read(), A[i].z = read(); for (int i = 1; i <= m; ++i) B[i].x = read(), B[i].y = read(), B[i].z = read(); for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) { g[i][j] = read(); cnt[j] += g[i][j]; int c = abs(A[i].x - B[j].x) + abs(A[i].y - B[j].y) + 1; add_edge(i, j + n, INF - g[i][j], c); add_edge(j + n, i, g[i][j], -c); } for (int i = 1; i <= n; ++i) { add_edge(S, i, A[i].z, 0); add_edge(i, S, 0, 0); } for (int i = 1; i <= m; ++i) { add_edge(i + n, T, B[i].z - cnt[i], 0); add_edge(T, i + n, cnt[i], 0); } int st = spfa(); if (st == -1) { puts("OPTIMAL"); return 0; } solve(st); puts("SUBOPTIMAL"); for (int i = 1; i <= n; ++i) for (int j = 1; j <= m; ++j) printf("%d%c", g[i][j], j == m ? '\n' : ' '); return 0; }