P1606 [USACO07FEB]Lilypad Pond G
P1606 [USACO07FEB]Lilypad Pond G
分析
这题初看,就能想到是求最短路计数。但是坑很多,所以我们换一个角度,我会首先将我的错误思路说一遍,再从中分析错误点,最后给出正解。
(我看到的时候,就觉得很简单,还好奇为什么是紫题,-_-,属实是小丑了)
错解
我们用dist[i] [j]表示以(i,j)结尾的路径中莲花的个数。f[i] [j]表示以(i,j)结尾的所有路径中最短路的路径个数
所以我们只需要,在从起点搜的时候,将边分为两类
- 一类是向水连边,即需要放置一朵莲花,边权为1
- 另一类是向除了岩石之外的其他点连边,边权为0
不难想到用双端队列广搜去进行优化。
结束了……吗?
我们再来重新审视一下
错误分析
其实问题出在这f[i] [j]的定义上,来让我们仔细看我们的定义
以(i,j)结尾的所有路径中最短路的路径个数
但是题目的问题是什么?
以(i,j)结尾的所有路径中最少摆放的荷花数的方案
这里,我们来看,其实我们的最短路dist定义是没有错的,就是最少摆放的荷花数。
但是当我们重新审视我们的搜索过程的时候,就会发现错误了。
因为,荷花可能同样的摆放方式,但是到(i,j)的时候走的是不同的0权道路
例如看下边的例子,我们从起点到终点的最短路径有两条,但是实际上摆放荷花的方案只有1
3 2 2
2 2 2
2 0 2
2 2 2
1 2 1
2 2 2
2 1 2
2 2 2
4 2 2
那解决方案是什么呢?
我们仔细观察后可以发现,我们在荷花之间的转移时不能被多次计算的,即一个联通块的荷花应该被计算一次
但这个联通块,我们需要重新定义一下
表示之间能够通过日字形走法所连接起来的联通块
这样,我们就解决了问题。
而这个建图方式也是蛮有意思的,不妨就着重也讲一下
将联通块当成一个独立的点,重新建图
如何建图呢?
我们采用递归的方式,代码是最好的解说方法,请直接看代码
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(g[i][j]==0||g[i][j]==3)//从起点(3)和每一个水(0)开始进行dfs
{
memset(st,0,sizeof st);//进去前将需要用到的判重数组清0
dfs(id[i][j],i,j);
}
void dfs(int u,int x,int y)
{
if(st[id[x][y]]) return ;//若是该边已经被经过,直接返回
st[id[x][y]] = 1;//若没有,则标记为true
for(int i=0;i<8;i++)
{
int nx = x + dx[i],ny = y + dy[i];
if(nx<0||nx>=n||ny<0||ny>=m||st[id[nx][ny]]) continue;
if(g[nx][ny]==1) dfs(u,nx,ny);//若是该点是荷花(1),则直接进行递归,
else if(g[nx][ny]!=2)//若是除2之外的点,则将该点向这个点连一条边
{
st[id[nx][ny]]=1;//标记这个点已经被扫过
add(u,id[nx][ny]);
}
}
}
如果不明白,我们可以再看下边的这个状态
3 2 2
2 2 2
2 0 2
2 2 2
1 2 1
2 2 2
2 1 2
2 2 2
4 2 2
其中的三个荷花就属于是一个联通块的,此时从这个联通块,就会把其当成一个点
再(2,1)的0向该联通块所能连接到的所有点连接一条边。
这样我们就避免的荷花之间的转移带来的多余路径。
其中有一些小问题需要注意
- 由于是从一个点0/3向荷花的联通块所能到达的所有点连边,因此边数会很大,不如我们就直接设置为N^4,避免麻烦
- 由于我们重新建图的时候,二维的坐标不容易建图,我们可以进行调整,将二维坐标变为一维的标号
AC_code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 35*35,M = N*50,INF = 0x3f3f3f3f;//边数需要开的大一些,因为从当前点,是向一个莲叶的连通块周边的所有点连边,不好估计,推荐直接弄一个N^4
int h[N],e[M],ne[M],idx;
int id[35][35],g[35][35];
int dist[N];
LL f[N];
bool st[N];
int dx[8] = {-2,-1,1,2,2,1,-1,-2},dy[8] = {1,2,2,1,-1,-2,-2,-1};
int S,E;
int n,m;
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
void dfs(int u,int x,int y)
{
if(st[id[x][y]]) return ;//若是该边已经被经过,直接返回
st[id[x][y]] = 1;//若没有,则标记为true
for(int i=0;i<8;i++)
{
int nx = x + dx[i],ny = y + dy[i];
if(nx<0||nx>=n||ny<0||ny>=m||st[id[nx][ny]]) continue;
if(g[nx][ny]==1) dfs(u,nx,ny);//若是该点是荷花(1),则直接进行递归,
else if(g[nx][ny]!=2)//若是除2之外的点,则将该点向这个点连一条边
{
st[id[nx][ny]]=1;//标记这个点已经被扫过
add(u,id[nx][ny]);
}
}
}
void spfa()
{
queue<int> q;
q.push(S);
memset(dist,0x3f,sizeof dist);
memset(st,0,sizeof st);
f[S] = 1,dist[S] = 0;
st[S] = 1;
while(q.size())
{
auto t = q.front();
q.pop();
st[t] = 0;
for(int i=h[t];~i;i=ne[i])
{
int j = e[i];
if(dist[j]>dist[t]+1)
{
dist[j] = dist[t] + 1;
f[j] = f[t];
if(!st[j])
{
st[j] = 1;
q.push(j);
}
}
else if(dist[j]==dist[t]+1)
f[j] += f[t];
}
}
if(dist[E]==INF) puts("-1");
else cout<<dist[E]-1<<endl<<f[E]<<endl;
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=0,k=0;i<n;i++)
for(int j=0;j<m;j++,k++){
cin>>g[i][j],id[i][j]=k;
if(g[i][j]==3) S = k;
else if(g[i][j]==4) E = k;
}
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(g[i][j]==0||g[i][j]==3)//从起点(3)和每一个水(0)开始进行dfs
{
memset(st,0,sizeof st);//进去前将需要用到的判重数组清0
dfs(id[i][j],i,j);
}
spfa();
return 0;
}

浙公网安备 33010602011771号