112. 作业之地理篇 最小费用最大流模板题
题面好像看不了吧,
思路是把相邻的点都建立一条边,然后跑最小费用最大流。把点hash成两部分,然后需要建立双向边,这样以后匹配数 / 2就是原图的最大匹配。
最小费用最大流感觉就是,用spfa代替了原来的bfs增广路,spfa的同时保证了cost最小,然后路径本来就是随意的,因为有了残余网络。可以让水流流回去。
#include <bits/stdc++.h> #define IOS ios::sync_with_stdio(false) using namespace std; #define inf (0x3f3f3f3f) typedef long long int LL; const int maxn = 30 * 30 * 4; struct Edge { int u, v, w, cost, tonext; }e[maxn * 2]; int first[maxn], num; int n, m; void addEdge(int u, int v, int w, int cost) { e[num].u = u, e[num].v = v, e[num].w = w, e[num].cost = cost, e[num].tonext = first[u]; first[u] = num++; } int x[maxn], y[maxn], DFN; int getID(int x, int y) { return (x - 1) * m + y; } int flow[maxn], pre[maxn]; int dis[maxn], tim[maxn]; bool in[maxn]; bool spfa(int bx, int n) { for (int i = 0; i <= n; ++i) { dis[i] = inf; tim[i] = 0; in[i] = false; flow[i] = 0; } queue<int> que; while (!que.empty()) que.pop(); que.push(bx), in[bx] = true, dis[bx] = 0, tim[bx]++, flow[bx] = inf; pre[bx] = -inf; while (!que.empty()) { int u = que.front(); que.pop(); for (int i = first[u]; ~i; i = e[i].tonext) { if (e[i].w > 0 && dis[e[i].v] > dis[e[i].u] + e[i].cost) { //²»Óñê¼Çflow dis[e[i].v] = dis[e[i].u] + e[i].cost; pre[e[i].v] = i; flow[e[i].v] = min(e[i].w, flow[u]); if (!in[e[i].v]) { que.push(e[i].v); in[e[i].v] = true; tim[e[i].v]++; } } } in[u] = false; } if (flow[n] == 0) return false; else return true; } LL ans; int maxFlow(int be, int en) { int sumFlow = 0; while (spfa(0, en)) { // cout << dis[en] << endl; ans += 1LL * dis[en] * flow[en]; int res = flow[en]; int edgeID = pre[en]; while (edgeID != -inf) { e[edgeID].w -= res; e[edgeID ^ 1].w += res; edgeID = pre[e[edgeID].u]; } sumFlow += res; } return sumFlow; } set< pair<int, int> > ss; void work() { memset(first, -1, sizeof first); num = 0, ++DFN; ss.clear(); int q; scanf("%d", &q); for (int i = 1; i <= q; ++i) { int xi, yi; scanf("%d%d", &xi, &yi); ss.insert(make_pair(xi, yi)); } for (int i = 1; i <= n; ++i) { for (int j = 1; j <= m - 1; ++j) { int val; scanf("%d", &val); if (ss.count(make_pair(i, j)) || ss.count(make_pair(i, j + 1))) continue; addEdge(getID(i, j), getID(i, j + 1) + n * m, 1, val); addEdge(getID(i, j + 1) + n * m, getID(i, j), 0, -val); addEdge(getID(i, j + 1), getID(i, j) + n * m, 1, val); addEdge(getID(i, j) + n * m, getID(i, j + 1), 0, -val); } } for (int i = 1; i <= n - 1; ++i) { for (int j = 1; j <= m; ++j) { int val; scanf("%d", &val); if (ss.count(make_pair(i, j)) || ss.count(make_pair(i + 1, j))) continue; addEdge(getID(i, j), getID(i + 1, j) + n * m, 1, val); addEdge(getID(i + 1, j) + n * m, getID(i, j), 0, -val); addEdge(getID(i + 1, j), getID(i, j) + n * m, 1, val); addEdge(getID(i, j) + n * m, getID(i + 1, j), 0, -val); } } if ((n * m - q) & 1) { printf("-1\n"); return; } for (int i = 1; i <= n * m; ++i) { addEdge(0, i, 1, 0); addEdge(i, 0, 0, 0); addEdge(n * m + i, 2 * n * m + 1, 1, 0); addEdge(2 * n * m + 1, n * m + i, 0, 0); } // for (int i = first[5]; ~i; i = e[i].tonext) { // printf("%d %d %d %d\n", e[i].u, e[i].v, e[i].w, e[i].cost); // } ans = 0; int res = maxFlow(0, 2 * n * m + 1); // cout << res << endl; if (res != (n * m - q)) { printf("-1\n"); } else printf("%lld\n", ans / 2); } int main() { #ifdef local freopen("data.txt", "r", stdin); // freopen("data.txt", "w", stdout); #endif while (scanf("%d%d", &n, &m) > 0) work(); return 0; }
这个本来是二分图,所以可以这样
也就是,
1 2 x
3 4 y
这样的格子
建立的边是
1 3
4 2
1连去2和3,然后2连去4和x,
然后3是连接4, 4是连接y
所以这样的一个图是二分图
至于这题
http://codeforces.com/contest/863/problem/B
吧n个人分成n/2组,每个人都可以搭配其他n - 1个人
这样的图,不是二分图
所以是做不了的
最大流、km算法有弊端(其实只是因为这个不是二分图)
当我把6个点分成3组的时候,相同的权值会引起重复选点的后果
比如我有6个点
1 3 4 6 3 4
两两搭配的费用是abs(a[i] - a[j])所以ans = 5
那么我km出来的结果是
1 5
2 1
3 6
4 3
5 2
6 4
这是因为1和5搭配的权值是2,2和1搭配的权值也是2.所以就不会自动选择1搭配5的同时,5搭配1,这样的话,1被多选了一次。。最终答案是8,除以2就是4,错误答案
既然选择了远方,就要风雨兼程~
posted on 2017-06-19 16:51 stupid_one 阅读(215) 评论(0) 编辑 收藏 举报