Luogu P1930 亚瑟王的宫殿 题解 [ 蓝 ] [ 分层图最短路 ] [ 枚举 ]

亚瑟王的宫殿:比较 tricky 的图论。

图论做法

思路

因为是无向图,所以考虑一个经典 trick,把所有点到集合点的距离之和化为集合点到其他所有点的位置之和,就可以从集合点做单源最短路了。

于是我们枚举集合点,从集合点 BFS 一遍,然后算出答案。

但是这题还有国王,有一个骑士要去接国王去集合点,于是考虑在枚举集合点后,继续枚举是哪个骑士去接国王。

那么某个骑士去接国王的代价如何计算呢?显然,骑士和国王必将在某一个点处会合,并且对于每一个会合位置,国王走的步数是确定的。因此我们可以把接国王的代价看作骑士移动的代价,也就是建立分层图,一层代表还没接到国王,一层代表接到了国王。每一层内部的边权都是 1,只有两层之间的边权为国王到某个点的代价。于是我们就可以用 dijkstra 快速计算骑士接到国王再到集合点的代价了。注意这一部分也要从集合点开始跑单源最短路。

时间复杂度 O(n2m2lognm)

注意特判只有一个国王以及集合点无法到达的情况。

代码

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
using piii=pair<int,pair<int,pi> >;
int n,m,kx,ky,ax[2005],ay[2005],s;
int d[50][50],dk[50][50],dis[2][50][50],ans=0x3f3f3f3f;
int gox[]={1,1,2,2,-1,-1,-2,-2};
int goy[]={2,-2,1,-1,2,-2,1,-1};
int gokx[]={0,0,1,1,1,-1,-1,-1};
int goky[]={1,-1,0,1,-1,0,1,-1};
bool legal(int x,int y){return (1<=x&&x<=n&&1<=y&&y<=m);}
void bfs_knight(int x,int y)
{
    memset(d,0x3f,sizeof(d));
    d[x][y]=0;
    queue<pi>q;
    q.push({x,y});
    while(!q.empty())
    {
        pi now=q.front();
        q.pop();
        int nx=now.fi,ny=now.se;
        for(int i=0;i<8;i++)
        {
            int tx=nx+gox[i],ty=ny+goy[i];
            if(legal(tx,ty)&&d[tx][ty]>=0x3f3f3f3f)
            {
                d[tx][ty]=d[nx][ny]+1;
                q.push({tx,ty});
            }
        }
    }
}
void bfs_king(int x,int y)
{
    memset(dk,0x3f,sizeof(dk));
    dk[x][y]=0;
    queue<pi>q;
    q.push({x,y});
    while(!q.empty())
    {
        pi now=q.front();
        q.pop();
        int nx=now.fi,ny=now.se;
        for(int i=0;i<8;i++)
        {
            int tx=nx+gokx[i],ty=ny+goky[i];
            if(legal(tx,ty)&&dk[tx][ty]>=0x3f3f3f3f)
            {
                dk[tx][ty]=dk[nx][ny]+1;
                q.push({tx,ty});
            }
        }
    }
}
void dijkstra(int x,int y)
{
    priority_queue<piii,vector<piii>,greater<piii> >q;
    memset(dis,0x3f,sizeof(dis));
    dis[0][x][y]=0;
    q.push({0,{0,{x,y}}});
    while(!q.empty())
    {
        piii now=q.top();
        q.pop();
        int lv=now.se.fi,nx=now.se.se.fi,ny=now.se.se.se;
        for(int i=0;i<8;i++)
        {
            int tx=nx+gox[i],ty=ny+goy[i];
            if(legal(tx,ty)&&dis[lv][tx][ty]>dis[lv][nx][ny]+1)
            {
                dis[lv][tx][ty]=dis[lv][nx][ny]+1;
                q.push({dis[lv][tx][ty],{lv,{tx,ty}}});
            }
        }
        if(lv==0)
        {
            if(dis[1][nx][ny]>dis[0][nx][ny]+dk[nx][ny])
            {
                dis[1][nx][ny]=dis[0][nx][ny]+dk[nx][ny];
                q.push({dis[1][nx][ny],{1,{nx,ny}}});
            }
        }
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    char c;
    int x,y;
    cin>>c>>x;
    y=c-'A'+1;
    kx=x,ky=y;
    while(cin>>c>>x){ax[s+1]=x,ay[s+1]=c-'A'+1;s++;}
    if(s==0)
    {
        cout<<0;
        return 0;
    }
    bfs_king(kx,ky);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            bool ilg=0;
            int res=0x3f3f3f3f,tot=0;
            bfs_knight(i,j);
            dijkstra(i,j);
            for(int k=1;k<=s;k++)
            {
                tot+=d[ax[k]][ay[k]];
                if(d[ax[k]][ay[k]]>=0x3f3f3f3f/2){ilg=1;break;}
            }
            if(ilg)continue;
            for(int k=1;k<=s;k++)res=min(res,tot-d[ax[k]][ay[k]]+dis[1][ax[k]][ay[k]]);
            ans=min(ans,res);
        }
    }
    cout<<ans;
    return 0;
}

搜索做法

比较没道理,但凡这题不限制行列的个数,把 nm 再加大点,都不至于让这种做法过去。但是这种做法还是有点意义的。

同样是枚举集合点。这次我们考虑骑士接国王最多要花费多少步。显然,因为棋盘很小,所以最多不会超过 10 步就能接到国王。这当然不是一个准确的下界,只是这样取比较保险,洛谷上的题解大多都取少了,可以被 hack 掉。

于是我们枚举国王走 10 步以内到的点,然后像上面一样暴力做就好了。

时间复杂度 O(n2m2×100)

代码没写。

posted @   KS_Fszha  阅读(3)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示