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 }
Vergil

金涛的代码就比较亲民了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 }
View Code

 

 

在这道题中学到了肥肠多的新想法,更深入理解了搜索算法。

本来最简单的dfsbfs我是不会的,但是学习了图论之后,就能感性理解,现在我们回过头去看那些最简单的:

bfs 我们常常使用队列实现,而我们要注意到,队列中的点都是待扩展节点。每次我们取出队头的顶点,遍历它能到达的全部状态。

posted @ 2018-08-29 20:05  cellur925&Chemist  阅读(143)  评论(0编辑  收藏  举报