[NOIP模拟题]chess(最短路计数)
题意
有一个\(m*n\)的棋盘\((n,m\leq 50)\),每个各自上有数字,一个马从起始位置出发跳向目标位置,走到0号格子会花费1代价,走到1号格子或者目标位置不花费代价,2号格子不能走,求\(S\)到\(T\)的最小代价及方案数(注意:两种方案不同当且仅当路径上的至少有一个不同的0号格子)
思路
下面的论述都不考虑2号格子
对于最小代价,很容易想到建图跑最短路:对于每个点向八个方向连边,如果是0号边权为1,1号边权为0,跑一遍dijkstra即可
对于方案数,如果两种方案不同的条件换成“两条路径上有一个点不同即可”且所有边权为1,这道题就是最短路计数
然而两条路径不同只取决于0号格子,也就是说,从一个0号格子到另外一个0号格子,不需要管中间怎么走,也就是说其实1号格子并没有任何作用,如果两个0号点之间全是1号点,我们可以跳过这些1号点使得0号点直接相连(当然如果两个0号点之间还有0号点就不用管了)
所以,从每一个0号点出发(起点终点也要算0号点),dfs一遍找出所有的中间只经过1号格子即可到达的0号点,两者直接连边,用vis标记使得一个点在一次dfs中只出现一次,避免重边或者反复横跳
这样做之后,整张图边权全为1且只剩下0号格子连有边,当作最短路计数做即可,注意由于目标格子不要代价,最终的最短路长度要-1(这样做就已经可以求出最小代价了,可以不用特意跑一次dijkstra)
Code:
#include<bits/stdc++.h>
#define N 2605
using namespace std;
typedef long long ll;
int n,m,S,T;
int a[55][55];
int dis[N],kkk;
int dox[10]={-1,-2,-2,-1,1,2,2,1},doy[10]={-2,-1,1,2,2,1,-1,-2};
bool vis[N];
vector<int> son[N];
ll ans[N];
struct Edge
{
int next,to,dis;
}edge[N<<4];int head[N],cnt=1;
void add_edge(int from,int to,int dis)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
edge[cnt].dis=dis;
head[from]=cnt;
}
template <class T>
void read(T &x)
{
char c;int sign=1;
while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
while((c=getchar())>='0'&&c<='9') x=x*10+c-48; x*=sign;
}
int get_id(int x,int y)
{
if(x<1||x>n||y<1||y>m) return -1;
return (x-1)*50+y;
}
void dfs(int root,int rt)
{
vis[rt]=1;
if(rt==S&&root!=rt) return;
if(a[(rt-1)/50+1][(rt-1)%50+1]%4==0&&rt!=root) {son[root].push_back(rt);return;}
for(int i=head[rt];i;i=edge[i].next)
{
int v=edge[i].to;
if(!vis[v]) dfs(root,v);
}
}
int main()
{
freopen("chess.in","r",stdin);
freopen("chess.out","w",stdout);
read(n);read(m);
for(int i=1;i<=n;++i)
{
for(int j=1;j<=m;++j)
{
read(a[i][j]);
if(a[i][j]==3) S=get_id(i,j);
if(a[i][j]==4) T=get_id(i,j);
}
}
for(int i=1;i<=n;++i)//建图
{
for(int j=1;j<=m;++j)
{
if(a[i][j]==2) continue;//可有可无
int now=get_id(i,j);
for(int k=0;k<8;++k)
{
int dx=i+dox[k],dy=j+doy[k];
int v=get_id(dx,dy);
if(v==-1||a[dx][dy]==2) continue;//出界或者踩到友军
if(a[dx][dy]==0||a[dx][dy]==3) add_edge(now,v,1);
else add_edge(now,v,0);
}
}
}
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
if(a[i][j]==0||a[i][j]==3)
{
memset(vis,0,sizeof(vis));
dfs(get_id(i,j),get_id(i,j));
}
}
memset(dis,-1,sizeof(dis));
queue<int> q;
ans[S]=1; q.push(S); dis[S]=0;
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=0;i<(int)son[u].size();++i)
{
int v=son[u][i];
if(dis[v]==-1)
{
if(v!=T) dis[v]=dis[u]+1,q.push(v);//此处也可以不特判,最后dis[T]-1即可
else dis[v]=dis[u];
}
if(dis[v]==dis[u]+1||(dis[v]==dis[u]&&v==T)) ans[v]+=ans[u];
}
}
if(dis[T]==-1) cout<<-1<<endl;
else cout<<dis[T]<<endl<<ans[T]<<endl;
return 0;
}