网络流强化-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
",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
",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
",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
",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
",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
",u,d[u]);
161                 //pt("BREAK FROM HERE
");
162                 break; // gap 优化
163             } 
164             ++num[d[u] = base+1]; 
165             //pt("------------------我来增加层次%d:%d
",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
",u);
173             }
174             else
175             {
176                 // pt("STILL AT S:%d
",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
",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("
");
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-上下界限制最大流