//byehdoj 4067 Random Maze - 最小费用流 + 贪心构图
//
//
//【题意】:给出n个点,m条边,入口s和出口t,对于每条边有两个值a,b,如果保留这条边需要花费;否则,移除这条边需要花费b。
// 题目要求用最小费用构造一个有向图满足以下条件:
// 1.只有一个入口和出口
// 2.所有路都是唯一方向
// 3.对于入口s,它的出度 = 它的入度 + 1
// 4.对于出口t,它的入度 = 它的出度 + 1
// 5.除了s和t外,其他点的入度 = 其出度
// 最后如果可以构造,输出最小费用;否则输出impossib。
//
//【题解】:参考了starvae的题解,加入了自己的见解。首先我们贪心构图,何为贪心构图?对于每条边只有两种操作,要么保留,要么删除,每种操作都对应一种费用a或b,然后我们总是选择费用小的操作。
// 这里定义两个数组in[],out[],in[u]表示u当前的入度,同理out[u]表示u当前的出度,sum存放初始费用。
// 对于每条边u v a b,如果a <= b,连边v->u,容量为1,费用为a-b,这样连边的意义是我们初始选择保留这条边u->v,所以in[v]++, out[u]++,sum+=a。
// 否则如果a > b,连边u->v,容量为1,费用为b-a,这样连边的意义是我们初始选择删除这条边,所以in[],out[]没有改变,sum+=b。
// PS:我们这样选择费用肯定是最小的,但是不一定满足出度入度的要求,所以我们要在此基础上作出相应的调整。
// 添加一条由t指向s的虚拟边,加入这条边之后就可以把所有点都看成一样了,in[s]++, out[t]++。
// 再添加一个超级源点ss和一个超级汇点tt,然后对于每个点i,连边两条边,ss->i,容量为in[i],费用0,i->tt,容量为out[i],费用为0.
// 跑一次最小费用最大流,如果最大流等于所有in[i]的和,那么说明可以构造这样的有向图,费用就是sum+mincost;否则就是impossible了。
//
// starvae加入超级源汇之后是这样连边的:
//
// 新建超级源汇S,T。
//
// 对于每个点i, 如果in[i] > out[i] . 建边S->i, 权值为0, 流量为in[i] – out[i];
//
// 否则的话 建边 i->T ,权值为0, 流量为out[i] – in[i];
//
//为什么我有一种有源有汇可行流的感觉;
//这一个是正正宗宗的过的代码。。。
//还有建图时不用建汇点到源点的边。
//还有要用out 与 in相减。。
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<queue>
#define maxn 300
#define MAXM 10000
#define INF 0x3f3f3f3f
#define inf 0x3f3f3f3f
using namespace std;
int n,m,s,t,NE,sumFlow;
int head[maxn];
bool vis[maxn];
int pp[maxn];
int dist[maxn];
int in[maxn];
int out[maxn];
struct node
{
int u,v,w,a,b;
}G[MAXM];
struct Edge
{
int u, v, cap, cost;
int next;
}edge[MAXM<<2];
void init()
{
NE = 0;///为边数
memset(head, -1, sizeof(head));
}
void addedge(int u, int v, int cap, int cost)
{
edge[NE].u = u; edge[NE].v = v; edge[NE].cap = cap; edge[NE].cost = cost;
edge[NE].next = head[u]; head[u] = NE++;
edge[NE].u = v; edge[NE].v = u; edge[NE].cap = 0; edge[NE].cost = -cost;
edge[NE].next = head[v]; head[v] = NE++;
}
bool SPFA(int s, int t, int n)
{
int i, u, v;
queue <int> qu;
memset(vis,false,sizeof(vis));
memset(pp,-1,sizeof(pp));
for(i = 0; i <= n; i++) dist[i] = INF;
vis[s] = true; dist[s] = 0;
qu.push(s);
while(!qu.empty())
{
u = qu.front(); qu.pop(); vis[u] = false;
for(i = head[u]; i != -1; i = edge[i].next)
{
v = edge[i].v;
if(edge[i].cap && dist[v] > dist[u] + edge[i].cost)
{
dist[v] = dist[u] + edge[i].cost;
pp[v] = i;
if(!vis[v])
{
qu.push(v);
vis[v] = true;
}
}
}
}
if(dist[t] == INF) return false;
return true;
}
int MCMF(int s, int t, int n) /// minCostMaxFlow
{
int flow = 0; /// 总流量
int i, minflow, mincost;
mincost = 0;
while(SPFA(s, t, n))
{
minflow = INF + 1;
for(i = pp[t]; i != -1; i = pp[edge[i].u])
if(edge[i].cap < minflow)
minflow = edge[i].cap;
flow += minflow;
for(i = pp[t]; i != -1; i = pp[edge[i].u])
{
edge[i].cap -= minflow;
edge[i^1].cap += minflow;
}
mincost += dist[t] * minflow;
}
sumFlow = flow; /// 最大流
return mincost;
}
void solve(int ca)
{
init();
bool flag=false;
int ss=0;int tt=n+1;int sum=0;
memset(in,0,sizeof(in));
memset(out,0,sizeof(out));
for(int i=1;i<=m;i++)
{
if(G[i].a>G[i].b)//开始删除
{
G[i].w=G[i].a-G[i].b;///注意这里反过来是因为边由删除到添加原来是b,这里减b+a则为a,注意这里的转换思想。
sum+=G[i].b;
addedge(G[i].u,G[i].v,1,G[i].w);
}
else
{
sum+=G[i].a;
in[G[i].v]++;
out[G[i].u]++;
G[i].w=G[i].b-G[i].a;//开始保留
addedge(G[i].v,G[i].u,1,G[i].w);///注意连边的方式二者要相反
}
}
//下面是要平衡出入度
int temp=0;
in[s]++;
out[t]++;
// addedge(t,s,1,0);//平衡度,这样就不用区分入口,出口了。。
for(int i=1;i<=n;i++)///我应该是这么建图吧
{
if(in[i]-out[i]>0)
addedge(ss,i,in[i]-out[i],0),temp+=in[i]-out[i];
else
addedge(i,tt,out[i]-in[i],0);
}
// for(int i=1;i<=n;i++)
// {
// addedge(ss,i,in[i],0);
// addedge(i,tt,out[i],0);
// temp+=in[i];
// }
int result=MCMF(0,n+1,n+2);//以下求最大流的姿势对么??
if(sumFlow==temp)
flag=true;
cout<<"Case "<<ca<<": ";
if(flag)
{
result+=sum;
cout<<result<<endl;
}
else
cout<<"impossible"<<endl;
}
int main()
{
int cas;
scanf("%d",&cas);
for(int ca=1;ca<=cas;ca++)
{
scanf("%d%d%d%d",&n,&m,&s,&t);
for(int i=1;i<=m;i++)
scanf("%d%d%d%d",&G[i].u,&G[i].v,&G[i].a,&G[i].b);//a为保留边的花费,b为删除边的花费
solve(ca);
}
}
HDU 4067 费用流
最新推荐文章于 2018-08-26 20:48:09 发布