【洛谷P1606】白银莲花池【最短路】
题目大意:
题目链接:https://www.luogu.org/problemnew/show/P1606
一个的网格中有一些点可以踩,另一些点不可以踩。一个人每次走动会以“日”字型走动,求最少要将几个点改变成可以踩的点才可以使得这个人可以从点到达点,以及方案数。
思路:
第一问能马上想到,但是第二问显然是最短路计数,所以考虑第一问也用最短路做。
最简单的思想就是每一个点往外面8个日字型点连边,若是不可踩的点边权为1,否则边权为0。但是这样最短路计数时会少算一些重复的点,导致答案错误。
所以考虑把边权为0的边去掉,只留下边权为1的边。所以可以考虑用建边。对于每一个点,用找到所有的与它距离为1的点,然后连边即可。
接下来就是套 最短路模板 和 最短路计数模板 了。
代码:
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#define mp make_pair
using namespace std;
typedef long long ll;
const int N=40,M=500010;
const int dx[]={0,-2,-1,1,2,-2,-1,1,2};
const int dy[]={0,-1,-2,2,1,1,2,-2,-1};
int n,m,S,T,tot,map[N][N],head[N*N],dis[N*N];
ll cnt[N*N];
bool vis[N*N];
struct edge
{
int next,to,dis;
}e[M];
int C(int x,int y)
{
return (x-1)*m+y;
}
void add(int from,int to,int dis)
{
e[++tot].to=to;
e[tot].dis=dis;
e[tot].next=head[from];
head[from]=tot;
}
void dfs(int root,int x,int y)
{
if (vis[C(x,y)]) return;
vis[C(x,y)]=1;
for (int i=1;i<=8;i++)
{
int xx=x+dx[i],yy=y+dy[i];
if (xx>=1 && xx<=n && yy>=1 && yy<=m && !vis[C(xx,yy)])
{
if (map[xx][yy]==1) dfs(root,xx,yy);
else if (map[xx][yy]!=2)
{
add(root,C(xx,yy),1);
vis[C(xx,yy)]=1;
}
}
}
}
void dij()
{
memset(dis,0x3f3f3f3f,sizeof(dis));
memset(vis,0,sizeof(vis));
priority_queue<pair<int,int> > q;
q.push(make_pair(0,S));
dis[S]=0;
cnt[S]=1;
while (q.size())
{
int u=q.top().second;
q.pop();
if (vis[u]) continue;
vis[u]=1;
for (int i=head[u];~i;i=e[i].next)
{
int v=e[i].to;
if (dis[v]>dis[u]+e[i].dis)
{
dis[v]=dis[u]+e[i].dis;
cnt[v]=cnt[u];
q.push(make_pair(-dis[v],v));
}
else if (dis[v]==dis[u]+e[i].dis)
cnt[v]+=cnt[u];
}
}
}
int main()
{
freopen("data.in","r",stdin);
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
scanf("%d",&map[i][j]);
if (map[i][j]==3) S=C(i,j);
if (map[i][j]==4) T=C(i,j);
}
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
if (map[i][j]!=1&&map[i][j]!=2)
{
memset(vis,0,sizeof(vis));
dfs(C(i,j),i,j);
}
dij();
if (dis[T]<1e9) cout<<dis[T]-1<<endl<<cnt[T]<<endl;
else printf("-1\n");
return 0;
}