题意:给你一个Random Maze的定义,一个起点终点,起点终点的出入度关系,差1,其余的点出入度相等,现在给一个有向图,每一个边可以选择留还是删,费用分别是a,b。问你此图要是变成Random Maze所要的最小费用。
想法:类似于混合欧拉路的思想,对于每一给边,留还是删问题是关键。对于原图有一部分的费用是一定要出的那就是所有边的留删费用最少的费用和。如果由留变成删,或者删变成留,我们只需要额外支付转换的费用就好了,那么对于某条边来说:Max(a,b)-Min(a.b)。当a<=b的时候,选择留下,实际上是要连a->b这条边,但是不知道是否正真的对于全局来说要留下这条边,为了可以有机会转换,连接b->a这条边但是对于出入度的更新还是要以a->b为标准的。当a>b的时候,选择删掉,实际上是不连边了,但是为可有机会转换,连接a->b这条边,但是出入度不用更新,因为实际是不要这个边的。为了让所有的点的入读和初度一样,我们借助网络流,跑最大流,这里的流,我们是要跑满流的,因为要所有点的出度和入度多要相等,还有就是每一条边有一个费用,表示这条路的花费,即对之前的边是否要转换。很容易想到用费用流。
建图:1.虚拟source和sink;
2.a<=b b->a连接一条容量为1(这条边只可以改变一次初度入度关系),费用为b-a的边。跟新indegree,outdegree;
3.a>b a->b连接一条容量为1,费用为a-b的边;
4.给你的起点和终点的出入度定义的就不相等,所以起点的入度和终点的出度分别加1;
5.source向所有indegree>outdegree的点连一条容量为出入度差费用为0的边。
解释:一个点v,他只需要增加indegree-outdegree的出度就可以达到出入度相等了,在之前建边时,如果建了一条v出去的边,这条边可能是要留下来的或是删除的边,如果是要留下来的边,那么它对v的入度有影响,这里可以表示这个边可能要删掉,通过增加他的出度,等价于减少它的入度。如果是要删除的边,这里表示,这条边可能不删除了,增加出度。
6.所有indegree<=outdegree向sink连一条容量为出入度差费用为0的边。
如果达到满流,表示可以构成Random Maze,输出费用流加上之前的固定值。
#include<iostream> #include<cstring> #include<cstdio> #include<queue> #define inf 0x7fffffff using namespace std; const int nodes=150; const int edges=10000; int n,m,ss,tt,s,t,totfee,sum1,sum; int in[nodes],out[nodes],head[nodes],cur[nodes],cnt; struct node { int u,v,next,fee,flow; }e[edges]; class DINIC { public: int spath() { queue<int>q; while(!q.empty()) q.pop(); for(int i=s;i<=t;i++) dis[i]=inf; memset(vis,0,sizeof(vis)); memset(pe,-1,sizeof(pe)); vis[s]=1; dis[s]=0; q.push(s); while(!q.empty()) { int u=q.front(); q.pop(); vis[u]=0; for(int i=head[u];i+1;i=e[i].next) { int v=e[i].v; if(dis[v]>dis[u]+e[i].fee&&e[i].flow>0) { dis[v]=dis[u]+e[i].fee; pe[v]=i; if(!vis[v]) { vis[v]=1; q.push(v); } } } } return dis[t]!=inf; } int Min(int a,int b) { if(a<b) return a; return b; } int dfs(int u,int flow) { int cost=0; if(u==t) { totfee+=dis[t]; return flow; } for(int i=head[u];i+1;i=e[i].next) { int v=e[i].v; if(pe[v]==i&&e[i].flow>0) { int min=dfs(v,Min(e[i].flow,flow-cost)); if(min>0) { e[i].flow-=min; e[i^1].flow+=min; cost+=min; if(cost==flow) break; } else pe[v]=-1; } } return cost; } int result() { int ans=0; while(spath()) { ans+=dfs(s,inf); } return ans; } private: int dis[nodes],vis[nodes],pe[nodes]; }dinic; void Init() { memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); memset(head,-1,sizeof(head)); cnt=totfee=0; sum1=sum=0; } void add(int a,int b,int flow,int fee) { e[cnt].u=a; e[cnt].v=b; e[cnt].flow=flow; e[cnt].fee=fee; e[cnt].next=head[a]; head[a]=cnt++; e[cnt].u=b; e[cnt].v=a; e[cnt].flow=0; e[cnt].fee=-fee; e[cnt].next=head[b]; head[b]=cnt++; } void judge() { int res=dinic.result(); if(res!=sum1||totfee==0) { printf("impossible\n"); return; } printf("%d\n",sum+totfee); } int main() { int tesk,ca=1; scanf("%d",&tesk); while(tesk--) { scanf("%d%d%d%d",&n,&m,&ss,&tt); Init(); for(int i=1;i<=m;i++) { int u,v,a,b; scanf("%d%d%d%d",&u,&v,&a,&b); if(a<=b) {//要这条边 sum+=a; add(v,u,1,b-a); out[u]++;in[v]++; } else {//不要这条边 sum+=b; add(u,v,1,a-b); } } in[ss]++;out[tt]++; s=0;t=n+1; for(int i=1;i<=n;i++) { if(in[i]>out[i]) { add(s,i,in[i]-out[i],0); sum1+=in[i]-out[i]; } else add(i,t,out[i]-in[i],0); } printf("Case %d: ",ca++); judge(); } return 0; }