网络流强化-HDU 3338-上下界限制最大流

Posted lighten-up-belief

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络流强化-HDU 3338-上下界限制最大流相关的知识,希望对你有一定的参考价值。

  题意是:

  一种特殊的数独游戏,白色的方格给我们填1-9的数,有些带数字的黑色方格,右上角的数字代表从他开始往右一直到边界或者另外一个黑格子,中间经过的白格子的数字之和要等于这个数字;左下角的也是一样的意思,只是作用对象成了它下方的白格子。

  思路:

  既然所有行的数字之和等于所有列的数字之和,那么我们可以将行方向(向右)的点作为与源点连接的点,列方向(向下)的点作为与汇点连接的点。

  由于向右和向下的点可能在同一块方格里面,以及我们需要设置每个白格子的容量,所以我们需要拆点。

  题目要求填1-9的数,所以白格子起码都是1,我们不妨假设这些基础的1已经填好了,那么将对应行方向的点的入边(来自源点)容量减去该点作用范围内的白格子数目,将对应列方向的点的出边(去到汇点)容量减去该点作用范围内的白格子数目。然后设置白格子的容量为8就能保证填的都是1-9的数了。

  

技术图片
  1 #include<cstdio>
  2 #include<cstring>
  3 #include<cmath>
  4 using namespace std;
  5 #define maxe 100128  //pay  双向边 一共10万条路 双向就是20万 反边就是40万
  6 #define maxv 20128   //pay
  7 #define maxn 105    //pay
  8 #define sc scanf
  9 #define pt printf
 10 #define rep(i,a,b)  for(int i=(a);i<(b);++i)
 11 const int inf = 0x3f3f3f3f; 
 12 int cg,sp,ins;  //cg change sp是总流量 ins是加速回溯点
 13 int N,M,s,t,delta,UP_LIMIT;
 14 int q[maxv],fro,rea;
 15 typedef struct ed
 16     int v,nxt,cap; //dis
 17 ed;
 18 ed e[maxe];
 19 int tot,head[maxv],cur[maxv],vis[maxv],bk[maxv],d[maxv],num[maxv]; //
 20 int mi(int a,int b) return a<b?a:b;
 21 int mx(int a,int b) return a>b?a:b;
 22 void add(int u,int v,int cap)
 23 
 24     e[tot].v=v;         e[tot].nxt=head[u];
 25     /*e[tot].dis=dis;*/     e[tot].cap=cap;
 26     head[u]=tot++;
 27 
 28     e[tot].v=u;         e[tot].nxt=head[v];
 29     /*e[tot].dis=-dis;*/    e[tot].cap=0;
 30     head[v]=tot++;
 31  
 32 // 仅有一次的BFS为ISAP节省了不少时间 
 33 bool bfs()
 34 
 35     //数组模拟queue
 36     int u,v,i,my_all = t;
 37     for(i=0;i<=my_all;++i) vis[i]=num[i]=0;  //无须一定是my_all,涉及所有即可~~~~~~~~~~~~~~~~~~ 
 38     for(i=0;i<=my_all;++i) d[i]=UP_LIMIT;    //UP_LIMIT的设定是比最高的层次大1
 39     fro = rea = 0;
 40     q[rea] = t; ++rea;
 41     vis[t] = 1;
 42     d[t] = 0;
 43     ++num[d[t]];
 44     while (rea>fro) 
 45     
 46         u = q[fro]; ++fro;
 47         //pt("BFSing~ : u=%d\\n",u);
 48         for (i=head[u]; i!=-1; i=e[i].nxt) 
 49         
 50             v=e[i].v;
 51             if (!vis[v] && e[i^1].cap ) 
 52             
 53                 vis[v] = true;
 54                 d[v] = d[u] + 1;
 55                 ++num[d[v]];
 56                 q[rea] = v; ++rea;
 57                 //pt("普度众生 u=%d,v=%d,d[u]=%d,d[v]=%d\\n",u,v,d[u],d[v]);
 58             
 59         
 60     
 61     //int last_ed=-1;
 62     //把连接了到不了终点的点的边都去除 如果确定都能和终点连接 就不需要 
 63     /*for(u=0;u<=my_all;++u)
 64     
 65         if(!vis[u]) continue;
 66         for (i=head[u]; i!=-1; i=e[i].nxt) 
 67         
 68             v=e[i].v;
 69             if(!vis[v])
 70             
 71                 if(i==head[u])
 72                     head[u] = e[i].nxt;
 73                     continue;
 74                 
 75                 else
 76                     e[last_ed].nxt = e[i].nxt;
 77                     continue;
 78                 
 79             
 80             last_ed = i;
 81         
 82     */
 83     return vis[s];
 84 
 85 // 增广
 86 int augment()/*单源单汇 不用查这个*/
 87 
 88     int  flow = inf, i;
 89     cg = t;
 90     // 从汇点到源点通过 p 追踪增广路径, flow 为一路上最小的残量
 91     while (cg != s) 
 92         i = bk[cg];
 93         if(flow>=e[i].cap)
 94         
 95             flow = e[i].cap;
 96             ins = e[i^1].v;     
 97             //用来加速寻找,在最小流量断开的地方重新开始寻找
 98             //嗯,等一下 我这个是从终点往起点寻找,而确定增光路径是从起点到终点
 99             //那么起点是河流的上游,那么回溯的河段应该尽可能的往上游靠近
100             //所以应该将flow>e[i].cap的大于号改成大于等于号
101         
102         cg = e[i^1].v;
103     
104     cg = t;
105     // 从汇点到源点更新流量
106     while (cg != s) 
107         i = bk[cg];
108         e[i].cap -= flow;
109         e[i^1].cap += flow;
110         cg = e[i^1].v;
111     
112     return flow;
113 
114 //由于每次修改层次的时候,都是在到剩下子节点的距离中挑选最短的加1 所以层次分明不会出现死循环 
115 int max_flow()
116 
117     int flow = 0,i,u,v;
118     bool advanced;
119     if(bfs()==false) return 0;
120     //pt("零层妖塔=%d\\n",d[s]);
121     u = s;
122     memcpy(cur, head, sizeof(head));
123     while (d[s] < UP_LIMIT)          //UP_LIMIT的设定是比最高的层次大1
124     //终点是0,那么起点所在层次最多是N-1 同理,不是d[s]<t
125     
126         if (u == t) 
127         
128             flow += augment();
129             u = ins;    //pay speed up
130         
131         advanced = false;
132         for (i = cur[u]; i!=-1; i=e[i].nxt) 
133          
134             v = e[i].v;
135             //pt("SELECT: %d -> %d\\n",u,v); 
136             if (e[i].cap && d[u] == d[v] + 1) 
137             
138                 advanced = true;
139                 bk[v] = i;
140                 cur[u] = i;
141               //  pt("%d -> %d ::d[u]=%d,d[v]=%d\\n",u,v,d[u],d[v]); 
142                 u = v;
143                 break;
144             
145         
146         if (!advanced) 
147          // retreat
148             int base = UP_LIMIT;        //UP_LIMIT的设定是比最高的层次大1
149             for (i = head[u]; i != -1; i=e[i].nxt)
150             
151                 if (e[i].cap&&base>d[e[i].v])
152                 
153                     cur[u] = i;
154                     base = d[e[i].v];
155                 
156             
157             
158             if (--num[d[u]] == 0)
159             
160                 //pt("u=%d,d=%d\\n",u,d[u]);
161                 //pt("BREAK FROM HERE\\n");
162                 break; // gap 优化
163              
164             ++num[d[u] = base+1]; 
165             //pt("------------------我来增加层次%d:%d\\n",m+1,u);
166             //我以前一直在想 如果没有找到怎么办呢 现在发现原来找不到的话距离会被赋成base+1 
167             //—— 比上界还高 所以接下来不会再访问到这个点 这个点也没有机会被减成0了——不会莫名其妙地break,哈哈哈
168             if (u != s)
169             
170                 //pt("from %d ",u);
171                 u = e[bk[u]^1].v;
172                 //pt("return to %d\\n",u);
173             
174             else
175             
176                 // pt("STILL AT S:%d\\n",s);
177             
178         
179     
180     return flow;
181 
182 
183 void init()
184 
185     tot=0;
186     memset(head,-1,sizeof(head));   //pay 
187 
188 char info[maxn][maxn][8];
189 int  ok_ed[maxn][maxn];
190 int is_black[maxn][maxn];
191 int LEFT[maxn][maxn],RIGHT[maxn][maxn];
192 //最大流部分没有什么问题了 关键在于终点源点、编号分配、题目理解建图上面
193 //切记:不要建立无用的边!! 包括连接不到的点 和不和终点连通的点 
194 //确保所有的点都要和终点连通——这样才是合法的点
195 int main()
196 
197     freopen("in.txt","r",stdin);
198     
199     /*数据初始化区*/
200     s=0, bk[0]=-1;
201 
202     /*变量存放区*/
203     int i,j,u,v,id,who,sub;
204 
205     while(~sc("%d%d",&N,&M))
206     
207 
208         /*数据初始化区*/
209         init(); sp = 0;
210         for(i=0;i<N;++i) for(j=0;j<M;++j) is_black[i][j] = 0;
211         for(i=0;i<N;++i) for(j=0;j<M;++j) LEFT[i][j] = RIGHT[i][j] = ok_ed[i][j] = -1;
212 
213         /*数据读取区*/
214         for(i=0;i<N;++i) for(j=0;j<M;++j) sc("%s",info[i][j]);
215 
216         /*关键数据赋值区*/
217         delta  = (N)*(M); t=2*delta+1; s=2*delta; 
218         UP_LIMIT = 2*delta + 2; 
219                 //说明:这个是层次的无法达到的上界,所以一共有N个点的时候,
220                 //如果从0开始编号,那么上界就是N;如果从1开始编号,上界就是N+1
221 
222         /*数据处理区*/ /*建图区*/
223         for(i=0;i<N;++i) for(j=0;j<M;++j)
224         
225             if(info[i][j][0]==.)  continue; 
226             is_black[i][j] = 1; 
227             if(info[i][j][0]!=X)  LEFT[i][j] = (info[i][j][0]-0)*100+(info[i][j][1]-0)*10+info[i][j][2]-0;
228             if(info[i][j][4]!=X) RIGHT[i][j] = (info[i][j][4]-0)*100+(info[i][j][5]-0)*10+info[i][j][6]-0;
229         
230         for(i=0;i<N;++i) for(j=0;j<M;++j)
231         
232             if(is_black[i][j]==0)  continue; 
233             if(LEFT[i][j]==-1&&RIGHT[i][j]==-1) continue;
234             
235             if(LEFT[i][j]!=-1)
236             
237                 id = i*M + j + delta; //往下的取小一点的编号
238                 v=j; u=i+1; sub = 0;
239                 while(u<N&&is_black[u][v]==0) ++sub,++u;
240                 LEFT[i][j]-=sub;
241                 add(id,t,LEFT[i][j]);
242             
243             if(RIGHT[i][j]!=-1)
244             
245                 id = i*M + j ; //往下的取小一点的编号
246                 u=i; v=j+1;  sub=0;
247                 while(v<M&&is_black[u][v]==0) ++sub,++v;
248                 RIGHT[i][j]-=sub;
249                 add(s,id,RIGHT[i][j]);
250             
251         
252         for(i=0;i<N;++i) for(j=0;j<M;++j)
253         
254             if(is_black[i][j])  continue;
255             id = i*M +j;            //!!!居然把这个放在后面,那建立边就会失败了,相当于用这次的容量帮上次的建边 
256             add(id,id+delta,8); ok_ed[i][j] = tot - 2; //value[i][j] - cap就是分配的流量 
257             
258             /*OUTPUT*/
259             v=j; u=i-1;
260             while(is_black[u][v]==0) --u;
261             who = u*M + v;
262             add(id+delta,who+delta,inf);
263             /*INPUT*/
264             u=i; v=j-1;
265             while(is_black[u][v]==0) --v;
266             who = u*M + v ;
267             add(who,id,inf);
268         
269         /*答案处理区*/
270         sp = max_flow();
271         //pt("sp=%d\\n",sp);
272         for(i=0;i<N;++i) for(j=0;j<M;++j)
273         
274             if(j>0) pt(" ");
275             if(is_black[i][j]==1) pt("_");
276             else
277             
278                 who = ok_ed[i][j];
279                 pt("%d", 9 - e[who].cap   ); 
280                 
281              
282             if(j==M-1) pt("\\n");
283         
284     
285     return 0;
286 
287 
288 /**
289  * 友情链接:
290  * http://www.renfei.org/blog/isap.html 解释ISAP原理
291  * https://www.cnblogs.com/bosswnx/p/10353301.html 使用的数据结构和我的比较相近 
292  */
HDU 3338 ISAP

   2019年8月18日17:11:56

  想要知道这个增广大概是怎么回事,因为我之前觉得好像都是线性的。

  假设先走S->A1->B1->T,之后B1->T已经满流,此时再走S->A2->B1->T,走不通了。

  那么可以从B1往回增广走到A1,然后从A1开始增广寻找新的路径,如A1->B2->T。

  技术图片

  

  

以上是关于网络流强化-HDU 3338-上下界限制最大流的主要内容,如果未能解决你的问题,请参考以下文章

网络流--最小费用最大流 (理解)

待更新算法

有源汇带上下界最小流

网络流网络流小总结

网络流

HDU 3157 Crazy Circuits (有源汇上下界最小流)