Destroying The Graph
Time Limit: 2000MS | Memory Limit: 65536K | |||
Total Submissions: 7976 | Accepted: 2571 | Special Judge |
Description
Alice assigns two costs to each vertex: Wi + and Wi -. If Bob removes all arcs incoming into the i-th vertex he pays Wi + dollars to Alice, and if he removes outgoing arcs he pays Wi - dollars.
Find out what minimal sum Bob needs to remove all arcs from the graph.
Input
Output
Sample Input
3 6 1 2 3 4 2 1 1 2 1 1 3 2 1 2 3 1 2 3
Sample Output
5 3 1 + 2 - 2 +
题意:给你一幅有向图, 对于点i删除所有进入该点的边就要支付费用W[i]+(情况1), 删除所有从该点出发的边就要支付费用W[i]-,问删除图中的所有边至少需要多少费用(情况2)。
分析:首先我们根据题意,选点就能删除一些边, 那么这可以看成是“用点去覆盖边”, 这里无非是把边分成了2类,
我们可以把原来的点进行拆点,那么就完完全全等价于“用点去覆盖边",如果支付费用都为1,那么这就是”最小点覆盖集“问题,但这题费用不确定,那么这就是“最小点权覆盖集”问题, 借助二分匹配的思想,我们可以引入“最小割”来解决“最小点权覆盖”问题。
建图:拆点,左点阵为情况2的点, 右点阵为情况1的点,右点阵跟汇点T连流量为W+,左点阵跟源点S连费用为W-,
对于输入的边<u, v> 连边 (u, v+n)费用为无穷大inf。跑一边最大流,求出最小费用。
输出解:最要我们找到一个满足条件的割边集(注意不是所有割边, 因为有一条流已经经过了一条割边,那么下面一条割边就不用选了,这样费用才是最小的),那么就能输出解了。怎么找出割边呢?我们可以在残余网络里走流,如果有一条边是割边,那么之后就流不过去了,不是割边还能继续流,具体实现我们可以从源点S用dfs搜出能走到的点标记vis[] =1,
那么对于边<u,v> 只要 vis[u] = 1 && vis[v] = 0 那就是割边了。
总结:二分匹配的题都可以用最大流来解,在二分图中 有 “最小点覆盖集”和“最打独立集”,如果有了点权,那么就要用最大流(最小割)来解决 “最小点权覆盖集”(最小割)和“最大点权独立集”(最大流)问题。
#include <cstdio>
#include <string.h>
#include <queue>
#include <algorithm>
using namespace std;
int const MAX = 55000;
int const inf = 0x3f3f3f3f;
struct Edge
{
int to, next;
int c;
}e[MAX];
int head[MAX], tot, s, t;
int def[MAX];
int n, m;
void add(int u, int v, int c)
{
e[++tot].to = v;
e[tot].c = c;
e[tot].next = head[u];
head[u] = tot;
}
int bfs()
{
queue<int> q;
while(!q.empty())
q.pop();
memset(def, 0, sizeof(def));
def[s] = 1;
q.push(s);
while(!q.empty()){
int u = q.front();
q.pop();
for(int i = head[u]; i != -1; i = e[i].next){
int v = e[i].to;
if(!def[v] && e[i].c > 0){
def[v] = def[u] + 1;
q.push(v);
}
}
}
return def[t];
}
int dfs(int u, int mi)
{
if(u == t) return mi;
int tmp;
for(int i = head[u]; i != -1; i = e[i].next){
int v = e[i].to;
if(e[i].c > 0 && def[v] == (def[u] + 1) && (tmp = dfs(v, min(mi, e[i].c))) > 0 ){
e[i].c -= tmp;
e[i+1].c += tmp;
return tmp;
}
}
return 0;
}
int dinic()
{
int ans = 0, tmp;
while(bfs()){
while(1){
tmp = dfs(0, inf);
if(tmp == 0)
break;
ans += tmp;
}
}
return ans;
}
int ok[MAX];
void DFS(int u)
{
ok[u] = 1;
for(int i = head[u]; i != -1; i = e[i].next){
int v = e[i].to;
if(ok[v] == 0 && e[i].c > 0)
DFS(v);
}
}
void solve()
{
memset(ok, 0, sizeof(ok));
int ans = dinic();
printf("%d\n", ans);
int res = 0;
DFS(0);
for(int i = 1; i <= n; i++){
/***********************************************************************************************************
巧妙地利用了本题图的特殊性质
A}中不可达的点就是应该执行1操作的点。 残余网络中,左侧不可达的点表示被操作1的流选中了
B}中可达的点就是应该执行2操作的点。 右侧可达的点表示从左侧开始无论如何也没有被流选中,只能留给操作2。
************************************************************************************************************/
if(!ok[i])
res++;
if(ok[i + n])
res++;
}
printf("%d\n", res);
for(int i = 1; i <= n; i++){
if(!ok[i])
printf("%d -\n", i);
if(ok[i+n])
printf("%d +\n", i);
}
}
int main()
{
int k;
while(scanf("%d %d", &n, &m) != EOF){
tot = 0;
memset(head, -1, sizeof(head));
s = 0, t = (n * 2) + 1;
for(int i = 1; i <= n; i++){
scanf("%d", &k);
add(i + n, t, k);
add(t, i + n, 0);
}
for(int i = 1; i <= n; i++){
scanf("%d", &k);
add(s, i, k);
add(i, s, 0);
}
for(int i = 1; i <= m; i++){
int u, v;
scanf("%d %d", &u, &v);
add(u, v + n, inf);
add(v + n, u, 0);
}
solve();
}
return 0;
}