题目链接:CodeForces-1082G Increasing Frequency
题意
有$n$个点,$m$条边,每个点和每条边都有一个权值,要求你从所给的图中选出一个子图,使得$sum{val_{edges}}-sum{val_{vertices}}$的值最大,子图的边集需满足:每条边关联的两个结点都在子图的点集中。
思路
闭合子图是选了结点 $u$ ,那么 $u$ 的所有后继结点也要选,而这道题是选了一条边,那么这条边的两个端点也要选,根据这个性质,把原图的边转换为结点,边权转换为点权,向两个端点连有向边,这样两个端点成为代表边的结点的后继结点,题目也就转换为了闭合子图问题。原图结点点权对答案贡献为负,所以点权取相反数改为负权值,边权对答案贡献为正,所以代表边的结点仍为正权值,题目即为求以上构建出的这个图的最大权闭合子图。
最大权闭合子图详解:传送门
代码实现
#include <iostream> #include <cstdio> #include <cstring> #include <queue> using std::queue; typedef long long LL; const int INF = 0x3f3f3f3f, N = 5010, M = 50010; int head[N], d[N]; int n, m, s, t, tot; LL maxflow; struct Edge { int to, cap, nex; } edge[M]; queue<int> q; void add(int x, int y, int z) { edge[++tot].to = y, edge[tot].cap = z, edge[tot].nex = head[x], head[x] = tot; edge[++tot].to = x, edge[tot].cap = 0, edge[tot].nex = head[y], head[y] = tot; } bool bfs() { // 在残存网络上构造分层图 memset(d, 0, sizeof(d)); while (q.size()) q.pop(); q.push(s); d[s] = 1; while (q.size()) { int x = q.front(); q.pop(); for (int i = head[x]; i; i = edge[i].nex) { int v = edge[i].to; if (edge[i].cap && !d[v]) { q.push(v); d[v] = d[x] + 1; if (v == t) return true; } } } return false; } int dinic(int x, int flow) { // 在当前分层图上增广 if (x == t) return flow; int rest = flow, k; for (int i = head[x]; i && rest; i = edge[i].nex) { int v = edge[i].to; if (edge[i].cap && d[v] == d[x] + 1) { k = dinic(v, std::min(rest, edge[i].cap)); if (!k) d[v] = 0; // 剪枝,去掉增广完毕的点(当前弧优化) edge[i].cap -= k; edge[i^1].cap += k; rest -= k; } } return flow - rest; } int main() { while (~scanf("%d %d", &n, &m)) { s = 0, t = n + m + 1; tot = 1, maxflow = 0; memset(head, 0, sizeof(head)); for (int i = 1, val; i <= n; i++) { scanf("%d", &val); add(i, n + m + 1, val); } LL sum = 0; for (int i = 1, u, v, val; i <= m; i++) { scanf("%d %d %d", &u, &v, &val); sum += val; add(0, n + i, val); add(n + i, u, INF); add(n + i, v, INF); } while (bfs()) maxflow += dinic(s, INF); printf("%I64d ", sum - maxflow); } return 0; }