HDU 4067 费用流

//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);
   }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值