[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
#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 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 
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 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<

转载于:https://www.cnblogs.com/Chtholly/p/11396820.html

你可能感兴趣的:([NOIP模拟题]chess(最短路计数))