LuoguP1606 [USACO07FEB]荷叶塘Lilypad Pond 【最短路】By cellur925
最短路好题!】
参考资料:学长 https://blog.csdn.net/TSOI_Vergil/article/details/52975779 学长太强了!!!%%%
==================算法部分=====================
我们读完题后,就能感受到浓浓的最短路氛围。我们可以将水与莲花间连边,水水间连边,边权为1;莲花间各自连边,边权为0。这样我们跑完一遍spfa后,就能得到摆放莲花的最小数量。但是第二问就很棘手,聪明的你也许会说跑一遍最短路计数就行了。但其实这是错误的算法。
比如在这张图中。正方形是莲花,圆是水(后连的莲花)
最短路条数为2,但有3个莲花。
或者更理性的说,如果经过的水面相同而经过的莲花不同,会被算作是不同的最短路,而实际上方案却是一样的。
不同的最短路要求很低,只要经过的点有一点不同便不同。而适合本题的求解则是只能水面不同,已有的莲花不同不算不同,也就是说 跑最短路时只经过水面。
再冷静分析我们会发现已有的莲花间的边是无意义的,只是“为了保证连通性”。同理,除起点和终点外的莲花与水之间的边也是没有意义的。
所以 我们考虑重新建图,对于每个水,对它进行bfs,把它和它能到达的水连边(或作可达性标记);将起点终点与水连边,这样跑最短路 。我们发现经过这样建图后最短路上的点除起点和终点是莲花之外,其余的点全是水,这样不同的最短路就对应着经过了不同的水,也就对应着不同的放莲花的方案。
以上有很多都参考Vergil学长的分析==
==================代码实现部分========================
然而算法说起来简单,实现就有些难了。学长给出的代码我太弱不能理解,于是便参考了金涛dalao的写法。从他的代码中对搜索有了更深理解。
先来观摩一下学长的代码,比较妙的地方是把矩阵的横纵坐标压成序号。
1 #include<cstdio> 2 3 #include<cstring> 4 5 #include<algorithm> 6 7 using namespace std; 8 9 #define maxn 130005 10 11 int n,m,g[35][35],sx,tx,sy,ty,l,cnt; 12 13 int pre[maxn],last[maxn],other[maxn]; 14 15 int quex[1000000],quey[1000000],que[1000000]; 16 17 const int dx[9]={0,1,1,2,2,-1,-1,-2,-2}; 18 19 const int dy[9]={0,-2,2,-1,1,-2,2,-1,1}; 20 21 int flag[35][35],dis[1005]; 22 23 bool vis[1005][1005],vist[maxn]; 24 25 long long f[1005]; 26 27 28 29 void connect(int x,int y) 30 31 { 32 33 l++; 34 35 pre[l]=last[x]; 36 37 last[x]=l; 38 39 other[l]=y; 40 41 } 42 43 44 45 void bfs(int fx,int fy) 46 47 { 48 49 flag[fx][fy]=++cnt; 50 51 int h=1,t=0; 52 53 for (int i=1;i<=8;i++) 54 55 { 56 57 int xx=fx+dx[i]; 58 59 int yy=fy+dy[i]; 60 61 if (xx<1||xx>n||yy<1||yy>m) continue; 62 63 if (flag[xx][yy]==cnt) continue; 64 65 flag[xx][yy]=cnt; 66 67 if (g[xx][yy]!=2&&g[xx][yy]!=0) 68 69 { 70 71 t++; 72 73 quex[t]=xx;quey[t]=yy; 74 75 int id1=(fx-1)*m+fy; 76 77 int id2=(xx-1)*m+yy; 78 79 if (vis[id1][id2]) continue; 80 81 connect(id1,id2); 82 83 connect(id2,id1); 84 85 vis[id1][id2]=vis[id2][id1]=1; 86 87 } 88 89 else if (g[xx][yy]==0) 90 91 { 92 93 int id1=(fx-1)*m+fy; 94 95 int id2=(xx-1)*m+yy; 96 97 if (vis[id1][id2]) continue; 98 99 connect(id1,id2); 100 101 connect(id2,id1); 102 103 vis[id1][id2]=vis[id2][id1]=1; 104 105 } 106 107 } 108 109 while (h<=t) 110 111 { 112 113 int x=quex[h],y=quey[h];h++; 114 115 for (int i=1;i<=8;i++) 116 117 { 118 119 int xx=x+dx[i]; 120 121 int yy=y+dy[i]; 122 123 if (xx<1||xx>n||yy<1||yy>m) continue; 124 125 if (flag[xx][yy]==cnt) continue; 126 127 flag[xx][yy]=cnt; 128 129 if (g[xx][yy]==0) 130 131 { 132 133 int id1=(fx-1)*m+fy; 134 135 int id2=(xx-1)*m+yy; 136 137 if (vis[id1][id2]) continue; 138 139 connect(id1,id2); 140 141 connect(id2,id1); 142 143 vis[id1][id2]=vis[id2][id1]=1; 144 145 } 146 147 else if (g[xx][yy]!=2&&g[xx][yy]!=0) 148 149 { 150 151 t++; 152 153 quex[t]=xx;quey[t]=yy; 154 155 } 156 157 } 158 159 } 160 161 } 162 163 164 165 void spfa(void) 166 167 { 168 169 memset(dis,53,sizeof dis); 170 171 dis[(sx-1)*m+sy]=0; 172 173 que[1]=(sx-1)*m+sy; 174 175 int h=1,t=1; 176 177 while (h<=t) 178 179 { 180 181 int u=que[h];h++; 182 183 vist[u]=0; 184 185 for (int p=last[u];p;p=pre[p]) 186 187 { 188 189 int v=other[p]; 190 191 if (dis[v]>dis[u]+1) 192 193 { 194 195 dis[v]=dis[u]+1; 196 197 if (!vist[v]) 198 199 { 200 201 que[++t]=v; 202 203 vist[v]=1; 204 205 } 206 207 } 208 209 } 210 211 } 212 213 } 214 215 216 217 void solve(void) 218 219 { 220 221 memset(vist,0,sizeof vist); 222 223 que[1]=(sx-1)*m+sy;f[que[1]]=1; 224 225 int h=1,t=1; 226 227 while (h<=t) 228 229 { 230 231 int u=que[h];h++; 232 233 for (int p=last[u];p;p=pre[p]) 234 235 { 236 237 int v=other[p]; 238 239 if (dis[v]==dis[u]+1) 240 241 { 242 243 f[v]+=f[u]; 244 245 if (!vist[v]) 246 247 { 248 249 que[++t]=v; 250 251 vist[v]=1; 252 253 } 254 255 } 256 257 } 258 259 } 260 261 } 262 263 264 265 int main() 266 267 { 268 269 scanf("%d%d",&n,&m); 270 271 for (int i=1;i<=n;i++) 272 273 for (int j=1;j<=m;j++) 274 275 { 276 277 scanf("%d",&g[i][j]); 278 279 if (g[i][j]==3) {sx=i;sy=j;} 280 281 if (g[i][j]==4) {tx=i;ty=j;} 282 283 } 284 285 bfs(sx,sy); 286 287 bfs(tx,ty); 288 289 for (int i=1;i<=n;i++) 290 291 for (int j=1;j<=m;j++) 292 293 if (g[i][j]==0) bfs(i,j); 294 295 spfa(); 296 297 if (dis[(tx-1)*m+ty]>1e7) 298 299 { 300 301 printf("-1\n"); 302 303 return 0; 304 305 } 306 307 printf("%d\n",dis[(tx-1)*m+ty]-1); 308 309 solve(); 310 311 printf("%lld\n",f[(tx-1)*m+ty]); 312 313 return 0; 314 315 }
金涛的代码就比较亲民了Orz。我仿照他的代码写了一份。注释里有解释。
1 #include<cstdio> 2 #include<queue> 3 #include<cstring> 4 #include<algorithm> 5 #include<utility> 6 7 using namespace std; 8 typedef long long ll; 9 10 int n,m,bx,by,ex,ey; 11 int mapp[40][40],dis[40][40]; 12 ll f[40][40];//记得开long long!! 13 bool vis[40][40]; 14 bool able[40][40][40][40]; 15 int dx[20]={0,1,2,2,1,-1,-2,-2,-1}; 16 int dy[20]={0,-2,-1,1,2,2,1,-1,-2}; 17 18 void floodfill(int x,int y) 19 {//洪水填充,也可以理解为bfs 20 memset(vis,0,sizeof(vis)); 21 queue<pair<int,int> > q; 22 q.push(make_pair(x,y)); 23 while(!q.empty()) 24 { 25 int nowx=q.front().first; 26 int nowy=q.front().second; 27 q.pop(); 28 for(int i=1;i<=8;i++) 29 { 30 int xx=nowx+dx[i]; 31 int yy=nowy+dy[i]; 32 if(xx<1||xx>n||yy<1||yy>m) continue; 33 if(vis[xx][yy]) continue;//vis数组实际在检查是不是莲花,但我们要找水 34 if(!mapp[xx][yy]||(xx==ex&&yy==ey)) 35 able[x][y][xx][yy]=1;//注意是x,y 我们洪水填充的目的是更新传进的x,y 36 // able相当于连边了 效果一样 37 else if(mapp[xx][yy]==1) 38 q.push(make_pair(xx,yy)),vis[xx][yy]=1; 39 //如果是莲花,就是待扩展节点,加入队列 40 } 41 } 42 } 43 44 void spfa_work() 45 {//边跑最短路边计数 46 memset(vis,0,sizeof(vis)); 47 queue<pair<int,int> >q; 48 q.push(make_pair(bx,by)); 49 memset(dis,127,sizeof(dis)); 50 vis[bx][by]=1;dis[bx][by]=0,f[bx][by]=1; 51 while(!q.empty()) 52 { 53 int nowx=q.front().first; 54 int nowy=q.front().second; 55 q.pop();vis[nowx][nowy]=0; 56 for(int i=1;i<=n;i++) 57 for(int j=1;j<=m;j++) 58 if(able[nowx][nowy][i][j]) 59 { 60 if(dis[i][j]>dis[nowx][nowy]+1) 61 { 62 dis[i][j]=dis[nowx][nowy]+1; 63 f[i][j]=f[nowx][nowy]; 64 if(!vis[i][j]&&(i!=ex||j!=ey)) 65 {//终点就不用再扩展了 66 vis[i][j]=1; 67 q.push(make_pair(i,j)); 68 } 69 } 70 else if(dis[i][j]==dis[nowx][nowy]+1) 71 f[i][j]+=f[nowx][nowy]; 72 } 73 } 74 } 75 76 int main() 77 { 78 scanf("%d%d",&n,&m); 79 for(int i=1;i<=n;i++) 80 for(int j=1;j<=m;j++) 81 { 82 scanf("%d",&mapp[i][j]); 83 if(mapp[i][j]==3) bx=i,by=j; 84 if(mapp[i][j]==4) ex=i,ey=j; 85 } 86 floodfill(bx,by); 87 //从起点出发 88 for(int i=1;i<=n;i++) 89 for(int j=1;j<=m;j++) 90 if(!mapp[i][j]) floodfill(i,j); 91 spfa_work(); 92 if(dis[ex][ey]>1000) 93 {//最多也才900个点 94 printf("-1"); 95 return 0; 96 } 97 else printf("%d\n%lld",dis[ex][ey]-1,f[ex][ey]); 98 //注意-1 以及lld 99 return 0; 100 }
在这道题中学到了肥肠多的新想法,更深入理解了搜索算法。
本来最简单的dfsbfs我是不会的,但是学习了图论之后,就能感性理解,现在我们回过头去看那些最简单的:
bfs 我们常常使用队列实现,而我们要注意到,队列中的点都是待扩展节点。每次我们取出队头的顶点,遍历它能到达的全部状态。