Random Maze(费用流 构图技巧)

原题: http://acm.hdu.edu.cn/showproblem.php?pid=4067

题意:

给出n个点,m条边,入口s和出口t,对于每条边有两个值a,b,如果保留这条边需要花费s;否则移除这条边花费b。

   要求构造:
   1.只有一个入口和出口
   2.所有路都是唯一方向
   3.对于入口s,它的出度 = 它的入度 + 1
   4.对于出口t,它的入度 = 它的出度 + 1
   5.除了s和t外,其他点的入度 = 其出度
   最后如果可以构造,输出最小费用;否则输出impossib。

解析:

in[i]为i的入度,out[i]为i的出度,sum为总费用

将所有的边默认为连上,再考虑删除的问题。我们将所有边的“反悔边”连上,如果流过这条边说明需要反悔,并且可以达到反悔(删除)的效果。

默认连上边(a,b,v_stay,v_cut):

  1. 维护度数组:out[a]++,in[b]++
  2. 维护费用:sum+=v_stay
  3. 在图中连反悔边:add(b,a,1,-v_stay+v_cut),反向是因为流过上这条边后in[a]++,out[b]++ 相当于out[a]–,in[b]–。花费加上-v_stay+v_cut,相当于sum+=v_cut

构图使得: 当流量到达最大流时,in[i]=out[i](这里为了所有点统一,将in[_sp]++,out[_ep]++)

将所有点的还需要匹配的度连接到图中:

for(int i=1;i<=n;i++){
	   if(in[i]>out[i])add(sp,i,in[i]-out[i],0);
	   else add(i,ep,out[i]-in[i],0);
}

将需要匹配的入度由源点引入,将需要匹配的出度引出到汇点。

那么通过流过其他边(相当于删除那些边),使得最大流等于入度的和,就代表了度的平衡。


但是TLE了,因为所有边都默认连上,但是可能有很多边都是要拆掉的,就不是很优秀了。

处理办法:

对比删除或保留所需要的花费,如果删除的更优,那么默认删除,添后悔删除的边

if(v_stay>v_cut){
    add(a,b,1,v_stay-v_cut);
    sum+=v_cut;//默认先删除
}

AC代码:

#include<stdio.h>
#include<iostream>
#include<math.h>
#include<algorithm>
#include<string.h>
#include<queue>
using namespace std;
#define LL long long
#define pill pair<int,int>
#define debug(i) printf("# %d\n",i)

const int inf=0x3f3f3f3f;
const LL infll=1e18;
const int N=110,M=20202;

int head[N],nex[M],to[M],val[M],cost[M],now;
void add(int a,int b,int v,int c){
    to[++now]=b;val[now]=v;cost[now]=c;nex[now]=head[a];head[a]=now;
    to[++now]=a;val[now]=0;cost[now]=-c;nex[now]=head[b];head[b]=now;
}

//*********************

int sp,ep,dis[N];

bool vis[N];
int pre[N];

bool SPFA(int &flow,int &cos){
    memset(vis,0,sizeof(vis));
    memset(pre,-1,sizeof(pre));
    for(int i=0;i<N;i++)dis[i]=inf;

    queue<int>Q;
    dis[sp]=0;vis[sp]=1;Q.push(sp);
    int d=inf;
    while(!Q.empty()){
        int p=Q.front();Q.pop();
        vis[p]=0;
        for(int i=head[p];~i;i=nex[i]){
            int u=to[i];
            if(val[i]>0&&dis[u]-cost[i]>dis[p]){
                dis[u]=dis[p]+cost[i];
                pre[u]=i;
                if(!vis[u]){
                    vis[u]=1;Q.push(u);
                }
            }
        }
    }
    if(dis[ep]==inf)return 0;
    for(int i=pre[ep];~i;i=pre[to[i^1]]){
        d=min(d,val[i]);
    }
    for(int i=pre[ep];~i;i=pre[to[i^1]]){
        val[i]-=d;
        val[i^1]+=d;
        cos+=cost[i]*d;
    }
    flow+=d;
    return 1;
}

pill MinCost(){
    int flow=0,cost=0;
    while(SPFA(flow,cost)){}
    return {flow,cost};
}

//***********************

int in[N],out[N],sum;

void init(){
    now=-1;//要求第一条边为0
    memset(head,-1,sizeof(head));
    sum=0;
    memset(in,0,sizeof(in));
    memset(out,0,sizeof(out));
}

//建反悔图,流过一条反悔删除边,相当于保留
int main(){int ca=0;int t;cin>>t;while(t--){
    init();
    sp=0,ep=N-1;
    int n,m,_sp,_ep;scanf("%d%d%d%d",&n,&m,&_sp,&_ep);
    in[_sp]++,out[_ep]++;//统一所有点:in==out

    for(int i=1;i<=m;i++){
        int a,b,v_stay,v_cut;
        scanf("%d%d%d%d",&a,&b,&v_stay,&v_cut);
        if(v_stay>v_cut){//添 用以反悔删除的边
            add(a,b,1,v_stay-v_cut);
            sum+=v_cut;//默认先删除
        }
        else{//添 反悔连接的边
            add(b,a,1,-v_stay+v_cut);//反向:因为这样反悔后in[a]++,out[b]++ 相当于out[a]--,in[b]--
            sum+=v_stay;
            out[a]++,in[b]++;//默认先连接
        }
    }
    //将还需要配对的度连入
    int sum_in=0;
    for(int i=1;i<=n;i++){
        if(in[i]>out[i])add(sp,i,in[i]-out[i],0),sum_in+=in[i]-out[i];
        else add(i,ep,out[i]-in[i],0);
    }

    pill ans=MinCost();
    if(ans.first!=sum_in)printf("Case %d: impossible\n",++ca);
    else printf("Case %d: %d\n",++ca,ans.second+sum);
}}

猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/86642025