题意是:
一种特殊的数独游戏,白色的方格给我们填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。
原文地址:https://www.cnblogs.com/lighten-up-belief/p/11372901.html
时间: 2024-10-08 06:51:11