题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3251
最大流最小割定理:s-t之间的最大流一定等于s-t之间的最小割的容量。最小割就是割除的正向边的最小容量之和使得源与漏之间不连通。
本题给出n个点,m条有向边,还有f个可用点,要求一个边割集使得s-t之间无流量,花费就是f个点的点权减去割边的总长度,也就是割边的花费。只需要用超级汇连接所给出f个点,然后用dinic算法求一次s-t最大流,最后一次增广的时候无法到达汇点,所以此时S能到达的点属于S集,其他点都是T能到达的点,也就构成了S-T点割集,将顶点在S集中,终点在T集中的边输出即时结果。
这道题有一个注意点就是为什么给出的f个点连向汇点的的边权是等于他们的点权的?我的解释是这样的,我们在最初把这些点的点权都加起来了,设置为sum,那么对于一个可选点a与汇点的连边e,如果dinic算法结束的时候该边的边权是0的话就说明这个点是不可取的,这条边就是割除的边中的一条,所以这个点的权是得不到的。如果边权大于零的话就说明这个点是源点无法到达的,所以也就是能选择的。所以如果要问选择的点是哪些的话可以扫描一遍这些边权来确定。其实,也可以从这个角度得知,割就是使得某条边的边权或者说容量为0。
代码如下:
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef unsigned int ui; 4 typedef long long ll; 5 typedef unsigned long long ull; 6 #define pf printf 7 #define mem(a,b) memset(a,b,sizeof(a)) 8 #define prime1 1e9+7 9 #define prime2 1e9+9 10 #define pi 3.14159265 11 #define lson l,mid,rt<<1 12 #define rson mid+1,r,rt<<1|1 13 #define scand(x) scanf("%llf",&x) 14 #define f(i,a,b) for(int i=a;i<=b;i++) 15 #define scan(a) scanf("%d",&a) 16 #define mp(a,b) make_pair((a),(b)) 17 #define P pair<int,int> 18 #define dbg(args) cout<<#args<<":"<<args<<endl; 19 #define inf 0x7ffffff 20 inline int read(){ 21 int ans=0,w=1; 22 char ch=getchar(); 23 while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();} 24 while(isdigit(ch))ans=(ans<<3)+(ans<<1)+ch-'0',ch=getchar(); 25 return ans*w; 26 } 27 const int maxn=1e5+10; 28 int n,m; 29 int eu[maxn*10],ev[maxn*10],ew[maxn*10],num[maxn*10]; 30 int t; 31 struct flow{ 32 int s,t; 33 int head[maxn],nxt[maxn*10],d[maxn],cur[maxn];//cur当前弧优化 34 bool vis[maxn]; 35 struct node{ 36 int u,v,w; 37 }p[maxn*10]; 38 int e; 39 void init() 40 { 41 e=0; 42 mem(head,-1); 43 mem(nxt,-1); 44 } 45 void addedge(int u,int v,int w)//顺带添加反向边 46 { 47 p[e].u=u; 48 p[e].v=v; 49 p[e].w=w; 50 nxt[e]=head[u]; 51 head[u]=e++; 52 p[e].u=v; 53 p[e].v=u; 54 p[e].w=0; 55 nxt[e]=head[v]; 56 head[v]=e++; 57 } 58 bool bfs(int src,int sink)//确定bfs序 59 { 60 mem(vis,0); 61 mem(d,0); 62 d[src]=1; 63 vis[src]=1; 64 queue<int> q; 65 q.push(src); 66 while(!q.empty()) 67 { 68 int cur=q.front(); 69 q.pop(); 70 for(int i=head[cur];~i;i=nxt[i]) 71 { 72 int v=p[i].v; 73 if(!vis[v]&&p[i].w)//确定这个点没有被标号,并且不是反向边 74 { 75 vis[v]=1; 76 d[v]=d[cur]+1; 77 q.push(v); 78 } 79 } 80 } 81 if(d[sink])return true; 82 return false; 83 } 84 int dfs(int s,int flow) 85 { 86 if(s==t)return flow; 87 int used=0; 88 for(int i=head[s];~i;i=nxt[i]) 89 { 90 int v=p[i].v,w=p[i].w; 91 if(d[v]==d[s]+1&&w>0)//根据Dinic算法的思想,只能走正向的、bfs序大一的边 92 { 93 int tmp=dfs(v,min(flow-used,w)); 94 if(tmp>0) 95 { 96 p[i].w-=tmp;//更新正向边的流量以及反向边的流量, 97 p[i^1].w+=tmp;//正向边是偶数,它对应的反向边就是正向边+1 98 used+=tmp;//从一个点出发最多的流量是flow,用掉的流量需要更新 99 if(used==flow)break; 100 } 101 } 102 } 103 if(!used)d[s]=0;//从该点出发的流不能被使用,所以这个点在这次搜索中被丢弃 104 return used; 105 } 106 int dinic() 107 { 108 int ans=0; 109 while(bfs(s,t)) 110 { 111 // memcpy(cur,head,sizeof(head)); 112 ans+=dfs(s,inf); 113 } 114 return ans; 115 } 116 }a; 117 int main() 118 { 119 //freopen("input.txt","r",stdin); 120 //freopen("output.txt","w",stdout); 121 std::ios::sync_with_stdio(false); 122 t=read(); 123 int f; 124 int k=0; 125 while(t--) 126 { 127 int u,v,w; 128 a.init(); 129 n=read(),m=read(),f=read(); 130 a.s=1;a.t=n+1; 131 f(i,1,m) 132 { 133 u=eu[i]=read(),v=ev[i]=read(),w=ew[i]=read();//保存的只有正向边 134 a.addedge(u,v,w); 135 } 136 int co=0; 137 f(i,1,f) 138 { 139 u=read(),v=read(); 140 co+=v; 141 a.addedge(u,a.t,v);//将选中点与汇点连边,边权为城市的价值 142 } 143 int ans=a.dinic(); 144 pf("Case %d: %d ",++k,co-ans); 145 int cnt=0; 146 f(i,1,m)//扫描所有的正向边 147 { 148 if(a.vis[eu[i]]&&!a.vis[ev[i]]) 149 { 150 num[cnt++]=i; 151 } 152 } 153 pf("%d",cnt); 154 f(i,0,cnt-1)pf(" %d",num[i]); 155 pf(" "); 156 } 157 }