原题: 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):
- 维护度数组:out[a]++,in[b]++
- 维护费用:sum+=v_stay
- 在图中连反悔边: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);
}}