【洛谷P1606】白银莲花池【最短路】

题目大意:

题目链接:https://www.luogu.org/problemnew/show/P1606
一个 n × m n\times m n×m的网格中有一些点可以踩,另一些点不可以踩。一个人每次走动会以“日”字型走动,求最少要将几个点改变成可以踩的点才可以使得这个人可以从 S S S点到达 T T T点,以及方案数。


思路:

第一问能马上想到 b f s bfs bfs,但是第二问显然是最短路计数,所以考虑第一问也用最短路做。
最简单的思想就是每一个点往外面8个日字型点连边,若是不可踩的点边权为1,否则边权为0。但是这样最短路计数时会少算一些重复的点,导致答案错误。
所以考虑把边权为0的边去掉,只留下边权为1的边。所以可以考虑用 d f s dfs dfs建边。对于每一个点,用 d f s dfs dfs找到所有的与它距离为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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值