【网络流】最大权闭合图
开始时看的 胡伯涛《最小割模型在信息学竞赛中的应用》这篇论文,有点没大看懂……太专业了,于是看了另一个的证明,现在放在下边:
• 证明:最小割为简单割。 引入一下简单割的概念:割集的每条边都与S或T关联。(请下面阅读时一定分清最小割与简单割,容易混淆) 那么为什么最小割是简单割呢?因为除S和T之外的点间的边的容量是正无穷,最小割的容量不可能为正无穷。所以,得证。 • 证明网络中的简单割与原图中闭合图存在一一对应的关系。(即所有闭合图都是简单割,简单割也必定是一个闭合图)。 证明闭合图是简单割:如果闭合图不是简单割(反证法)。那么说明有一条边是容量为正无穷的边,则说明闭合图中有一条出边的终点不在闭合图中,矛盾。 证明简单割是闭合图:因为简单割不含正无穷的边,所以不含有连向另一个集合(除T)的点,所以其出边的终点都在简单割中,满足闭合图定义。得正。 • 证明最小割所产生的两个集合中,其源点S所在集合(除去S)为最大权闭合图。 首先我们记一个简单割的容量为C,且S所在集合为N,T所在集合为M。 则C=M中所有权值为正的点的权值(即S与M中点相连的边的容量)+N中所有权值为负的点权值的绝对值(即N中点与T中点相连边的容量)。记(C=x1+y1);
(很好理解,不理解画一个图或想象一下就明白了)。 我们记N这个闭合图的权值和为W。 则W=N中权值为正的点的权值-N中权值为负的点的权值的绝对值。记(W=x2-y2); 则W+C=x1+y1+x2-y2。 因为明显y1=y2,所以W+C=x1+x2; x1为M中所有权值为正的点的权值,x2为N中权值为正的点的权值。 所以x1+x2=所有权值为正的点的权值之和(记为TOT). 所以我们得到W+C=TOT.整理一下W=TOT-C. 到这里我们就得到了闭合图的权值与简单割的容量的关系。 因为TOT为定值,所以我们欲使W最大,即C最小,即此时这个简单割为最小割,此时闭合图为其源点S所在集合(除去S)。得正。 至此,我们就将最大权闭合图问题转化为了求最小割的问题。求最小割用最小割容量=最大流,即可将问题转化为求最大流的问题。
这个看起来就很明白了,于是算是学会了最基础的最大权闭合图,挑战了两道题目。
【解析】把每个用户群当做节点,向汇点连一条边,容量为利益,需要的那两个中转站分别向它连边,流量正无穷,源点向每个中转站连边,容量为建筑费用,然后就是求出最大流,用所有的利益和-最大流就是答案了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /** 2 *Prob : profit 3 *Data : 2012-6-29 4 *SOl : 最大权闭合图 5 *Author : ZhouHang 6 */ 7 8 #include <cstdio> 9 #include <cstring> 10 #include <algorithm> 11 12 #define MaxN 60000 13 #define MaxE 400000 14 #define oo 20000000 15 16 using namespace std; 17 18 struct node { 19 int y,c,other,next; 20 } e[MaxE]; 21 int n,m,tot=0,s,t; 22 int dis[MaxN],pre[MaxN],cur[MaxN],a[MaxN],gap[MaxN]; 23 24 void insert(int x,int y,int c,int _other) 25 { 26 e[tot].y = y; e[tot].c = c; e[tot].other = _other; 27 e[tot].next = a[x]; a[x] = tot; 28 } 29 30 int SAP() 31 { 32 memset(pre,0,sizeof(pre)); 33 memset(gap,0,sizeof(gap)); 34 memset(dis,0,sizeof(dis)); 35 gap[0] = n; int u = pre[s] = s; 36 int maxflow = 0, aug = oo; 37 for (int i=s; i<=t; i++) 38 cur[i] = a[i]; 39 40 while (dis[s]<n) { 41 loop: for (int &i=cur[u]; i; i=e[i].next) { 42 int v = e[i].y; 43 if (e[i].c && dis[u]==dis[v]+1) { 44 aug = min(aug,e[i].c); 45 pre[v] = u; u = v; 46 if ( v == t ) { 47 maxflow += aug; 48 for (u=pre[u]; v!=s; v=u,u=pre[u]) { 49 e[cur[u]].c -= aug; 50 e[e[cur[u]].other].c += aug; 51 } 52 aug = oo; 53 } 54 goto loop; 55 } 56 } 57 int mindis = n-1; 58 for (int i=a[u]; i; i=e[i].next) { 59 int v = e[i].y; 60 if (e[i].c && mindis>dis[v]) { 61 cur[u] = i; mindis = dis[v]; 62 } 63 } 64 if ((--gap[dis[u]])==0) break; 65 dis[u] = mindis+1; gap[dis[u]]++; 66 u = pre[u]; 67 } 68 return maxflow; 69 } 70 71 int main() 72 { 73 freopen("profit.in","r",stdin); 74 freopen("profit.out","w",stdout); 75 76 int tmp,x,y,c; 77 78 scanf("%d%d",&n,&m); 79 s=0; t=n+m+1; 80 for (int i=1; i<=n; i++) { 81 scanf("%d",&tmp); 82 tot++; insert(s,i,tmp,tot+1); 83 tot++; insert(i,s,0,tot-1); 84 } 85 int sum = 0; 86 for (int i=1; i<=m; i++) { 87 scanf("%d%d%d",&x,&y,&c); 88 sum+=c; 89 //x-->n+i 90 tot++; insert(x,n+i,oo,tot+1); 91 tot++; insert(n+i,x,0,tot-1); 92 //y-->n+i 93 tot++; insert(y,n+i,oo,tot+1); 94 tot++; insert(n+i,y,0,tot-1); 95 //n+i-->t 96 tot++; insert(n+i,t,c,tot+1); 97 tot++; insert(t,n+i,0,tot-1); 98 } 99 100 n += m+2; 101 int ans = sum - SAP(); 102 103 printf("%d\n",ans); 104 105 106 fclose(stdin); fclose(stdout); 107 return 0; 108 }
【解析】题目比较简单,很明显的最大权闭合图问题,但是要注意的是有可能存在无敌节点,也就是说相互保护,这些点都不能消灭,要先找出来,去除掉,所以要先把图反着建,也就是说和结局最大权闭合图的网络图反过来,以保证能找到所有的可以消灭的点,然后用这些点来构建网络,做最大流……
而且构图的时候技巧很多,具体看代码吧。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 /** 2 *Prob : NOI 2009 pvz 3 *Dota : 2012-6-29 4 *Sol : 拓扑+最大权闭合图 5 *Author : ZhouHang 6 */ 7 8 #include <cstdio> 9 #include <cstring> 10 #include <queue> 11 #include <algorithm> 12 13 #define MaxR 30 14 #define MaxL 40 15 #define MaxN 1200 16 #define MaxE 400000 17 #define oo 20000000 18 19 using namespace std; 20 21 //拓扑图 22 struct node2 { 23 int y,next; 24 } e2[MaxE]; 25 bool vis[MaxN]; 26 int tot2=0,a2[MaxN]; 27 //网络流图 28 struct node { 29 int y,c,other,next; 30 } e[MaxE]; 31 int tot=0,s,t; 32 int dis[MaxN],pre[MaxN],cur[MaxN],gap[MaxN],a[MaxN]; 33 //其余结构 34 int n,m,sum=0; 35 int sc[MaxR*MaxL]; 36 int now=0,map[MaxR][MaxL],r[MaxN]; 37 38 39 void insert2(int x,int y) 40 { 41 e2[++tot2].y = y; 42 e2[tot2].next = a2[x]; a2[x] = tot2; 43 } 44 45 void init() 46 { 47 scanf("%d%d",&n,&m); 48 49 for (int i=0; i<n; i++) 50 for (int j=0; j<m; j++) 51 map[i][j] = ++now; 52 int num,x,y; 53 for (int i=0; i<n; i++) 54 for (int j=0; j<m; j++) 55 { 56 scanf("%d%d",&sc[map[i][j]],&num); 57 for (int k=1; k<=num; k++) 58 { 59 scanf("%d%d",&x,&y); 60 r[map[x][y]]++; 61 insert2(map[i][j],map[x][y]); 62 } 63 } 64 for (int i=0; i<n; i++) 65 for (int j=0; j<m-1; j++) 66 { 67 r[map[i][j]]++; 68 insert2(map[i][j+1],map[i][j]); 69 } 70 } 71 //找出可以消灭的植物 72 void Before_Network() 73 { 74 queue<int> q; 75 memset(vis,false,sizeof(vis)); 76 for (int i=1; i<=now; i++) 77 if (r[i]==0) { 78 q.push(i); 79 vis[i] = true; 80 } 81 while (!q.empty()) { 82 int u = q.front(); 83 q.pop(); 84 for (int i=a2[u]; i; i=e2[i].next) 85 if ((--r[e2[i].y])==0) { 86 q.push(e2[i].y); 87 vis[e2[i].y] = true; 88 } 89 } 90 } 91 void insert(int x,int y,int c,int _other) 92 { 93 if (tot==94583){ 94 bool flag = false; 95 } 96 e[tot].y=y; e[tot].c = c; e[tot].other=_other; 97 e[tot].next=a[x]; a[x]=tot; 98 } 99 int SAP() 100 { 101 memset(pre,0,sizeof(pre)); 102 memset(dis,0,sizeof(dis)); 103 memset(gap,0,sizeof(gap)); 104 for (int i=1; i<=n; i++) 105 cur[i] = a[i]; 106 gap[0]=n; pre[s]=s; int u=s,aug=oo; 107 int maxflow=0; 108 109 while (dis[s]<n) { 110 loop: for (int &i=cur[u]; i; i=e[i].next) { 111 int v=e[i].y; 112 if (e[i].c && dis[u]==dis[v]+1) { 113 aug = min(aug,e[i].c); 114 pre[v] = u; u = v; 115 if (v == t) { 116 maxflow += aug; 117 for (u=pre[u]; v!=s; v=u,u=pre[u]) { 118 e[cur[u]].c -= aug; 119 e[e[cur[u]].other].c += aug; 120 } 121 aug = oo; 122 } 123 goto loop; 124 } 125 } 126 int mindis = n-1; 127 for (int i=a[u]; i; i=e[i].next) { 128 int v = e[i].y; 129 if (e[i].c && mindis>dis[v]) { 130 cur[u] = i; 131 mindis = dis[v]; 132 } 133 } 134 if ((--gap[dis[u]])==0) break; 135 dis[u] = ++mindis; 136 gap[dis[u]]++; 137 u = pre[u]; 138 } 139 return maxflow; 140 } 141 void Make_G() 142 { 143 s = 0; t = 1; 144 int num=0; 145 for (int i=1; i<=now; i++) 146 if (vis[i] && sc[i]>0) sum += sc[i]; 147 148 memset(e,0,sizeof(e)); 149 memset(a,0,sizeof(a)); 150 151 for (int i=1; i<=now; i++) 152 if (vis[i]) { 153 num++; 154 for (int j=a2[i]; j; j=e2[j].next) { 155 int v = e2[j].y; 156 if (vis[v]) { 157 tot++; insert(v,i,oo,tot+1); 158 tot++; insert(i,v,0,tot-1); 159 } 160 } 161 } 162 t += num; 163 n = num+2; 164 165 for (int i=1; i<=now; i++) { 166 if (vis[i] && sc[i]>0) { 167 tot++; insert(s,i,sc[i],tot+1); 168 tot++; insert(i,s,0,tot-1); 169 } 170 if (vis[i] && sc[i]<0) { 171 tot++; insert(i,t,-sc[i],tot+1); 172 tot++; insert(t,i,0,tot-1); 173 } 174 } 175 176 } 177 178 int main() 179 { 180 freopen("pvz.in","r",stdin); 181 freopen("pvz.out","w",stdout); 182 183 init(); 184 Before_Network(); 185 Make_G(); 186 187 int ans = sum - SAP(); 188 189 printf("%d\n",ans); 190 191 fclose(stdin); fclose(stdout); 192 return 0; 193 }
感觉还可以,并没有觉得很难,编程复杂度说高不高,到现在SAP写了好多次了,应该很熟练了,下一步就该看看费用流了,听说zkw流很强大……要彻底研究它……