【网络流】最大权闭合图

  开始时看的 胡伯涛《最小割模型在信息学竞赛中的应用》这篇论文,有点没大看懂……太专业了,于是看了另一个的证明,现在放在下边:

•    证明:最小割为简单割。 
        引入一下简单割的概念:割集的每条边都与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)。得正。 至此,我们就将最大权闭合图问题转化为了求最小割的问题。求最小割用最小割容量=最大流,即可将问题转化为求最大流的问题。


这个看起来就很明白了,于是算是学会了最基础的最大权闭合图,挑战了两道题目。

 

【NOI 2006】最大获利

【解析】把每个用户群当做节点,向汇点连一条边,容量为利益,需要的那两个中转站分别向它连边,流量正无穷,源点向每个中转站连边,容量为建筑费用,然后就是求出最大流,用所有的利益和-最大流就是答案了。

NOI 2006 Profit
  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 }

 

【NOI 2009】植物大战僵尸

【解析】题目比较简单,很明显的最大权闭合图问题,但是要注意的是有可能存在无敌节点,也就是说相互保护,这些点都不能消灭,要先找出来,去除掉,所以要先把图反着建,也就是说和结局最大权闭合图的网络图反过来,以保证能找到所有的可以消灭的点,然后用这些点来构建网络,做最大流……

而且构图的时候技巧很多,具体看代码吧。

NOI 2009 pvz
  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流很强大……要彻底研究它……

 

posted @ 2012-06-29 22:43  守護N1身边  阅读(288)  评论(0编辑  收藏  举报