[NOIP2017模拟][bzoj2143] 飞飞侠

2017.10.31 T2 2024

题目背景
bzoj2143

题目描述
飞飞国是一个传说中的国度,国家的居民叫做飞飞侠。飞飞国是一个N×M的矩形方阵,每个格子代表一个街区。然而飞飞国是没有交通工具的。飞飞侠完全靠地面的弹射装置来移动。每个街区都装有弹射装置。使用弹射装置是需要支付一定费用的。而且每个弹射装置都有自己的弹射能力。我们设第i行第j列的弹射装置有Aij的费用和Bij的弹射能力。并规定有相邻边的格子间距离是1。那么,任何飞飞侠都只需要在(i,j)支付Aij的费用就可以任意选择弹到距离不超过Bij的位置了。如下图
这里写图片描述
(从红色街区交费以后可以跳到周围的任意蓝色街区。) 现在的问题很简单。有三个飞飞侠,分别叫做X,Y,Z。现在它们决定聚在一起玩,于是想往其中一人的位置集合。告诉你3个飞飞侠的坐标,求往哪里集合大家需要花的费用总和最低。

输入格式
输入的第一行包含两个整数N和M,分别表示行数和列数。接下来是2个N×M的自然数矩阵,为Aij和Bij 最后一行六个数,分别代表X,Y,Z所在地的行号和列号。

输出格式
第一行输出一个字符X、Y或者Z。表示最优集合地点。第二行输出一个整数,表示最小费用。如果无法集合,只输出一行NO

样例数据
输入

4 4
0 0 0 0
1 2 2 0
0 2 2 1
0 0 0 0
5 5 5 5
5 5 5 5
5 5 5 5
5 5 5 5
2 1 3 4 2 2

输出

Z
15

备注
【数据范围】
对于100%的数据,1<=N, M<=150;0<=Aij <= 109 ; 0<=Bij<=1000

分析:考场上看到这道题就只想到了bfs……还信心满满觉得能过50%,结果15%,bfs不是O( N )的……
首先,一种新颖的方法叫“建图求最短路”,给每个点能到达的点连上单向边,跑dijkstra,但是边的数量太大了,领接表都存不下,所以又一种新颖的做法叫“云里雾里dijkstra”(摘自题解):
取图中最远可能弹射的距离H。(H<=N+M-2)
引入“云端”这个新概念。
我们建立一个包含N*M*(S+1)个点的图dis[1…N][1..M][0…S]
其中dis[x][y][0]代表地面,也就是原图的街区。
dis[x][y][h]代表一个云端节点。
当居民在云端节点上时,他必须选择往低一层的相邻云端降落。
也就是说,dis[x][y][h]有5条出边:
dis[x][y][h]到dis[x][y][h-1]
dis[x][y][h]到dis[x-1][y][h-1]
dis[x][y][h]到dis[x+1][y][h-1]
dis[x][y][h]到dis[x][y-1][h-1]
dis[x][y][h]到dis[x][y+1][h-1]
它们的边权都是0.
也就是说,在高度为1的云端可以降落到地面的5个相邻节点。
弹射装置的作用实际上就是把飞飞侠从地面弹到同经纬的一定高度的云端上(所以你想,从那个高度一直往下走五个方向,走到地面形成的范围就正好是弹射装置能弹射的范围,所以向下走权值为0)。
如果a[x][y]=v(弹射能力),那么我们建边dis[x][y][0]到dis[x][y][v],边权(长度)为b[x][y](弹射费用)。
这样一来每个云端点只有5条出边,每个地面点只有一条出边,也就是说,这是一个相当稀疏的图,点数和边数同级,我们建立了一个点数和边数都是O(N*M*S)的图,同样是求3个地面点相互的最短路。
最坏时间复杂度:O(N3logN)。空间复杂度O( N3 )。
妈耶,怎么这么多......

其实,悄悄告诉你,不建边的dijkstra(直接跟着原图走,枚举能到的地方,不用邻接表了)在bzoj上也可以过哦~

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cctype>
#include<iomanip>
#include<queue>
#include<set>
using namespace std;

int getint()
{
    int sum=0,f=1;
    char ch;
    for(ch=getchar();!isdigit(ch)&&ch!='-';ch=getchar());
    if(ch=='-')
    {
        f=-1;
        ch=getchar();
    }
    for(;isdigit(ch);ch=getchar())
        sum=(sum<<3)+(sum<<1)+ch-48;
    return sum*f;
}

const int maxn=205;
const int fx[5]={0,-1,0,1,0};
const int fy[5]={1,0,-1,0,0};
int n,m,H;
int a[maxn][maxn],b[maxn][maxn];
long long dis[maxn][maxn][maxn*2],cost[4][4];
struct node{
    int x,y,h;
    friend inline bool operator < (const node &a,const node &b)
    {
        return a.h<b.h;
    }
}hom[4];
priority_queue<pair<long long,node> > que;

void dijkstra(int s,int t1,int t2)
{
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            for(int k=0;k<=H;++k)
                dis[i][j][k]=1e18;//赋初值

    int x,y,d,h;
    bool bz1=0,bz2=0;
    while(!que.empty()) que.pop();
    x=hom[s].x,y=hom[s].y,h=hom[s].h;
    dis[x][y][h]=b[x][y];//先从起点的地面弹到起点的云端
    que.push(make_pair(-dis[x][y][h],(node){x,y,h}));//入队
    while(!que.empty())
    {
        node u=que.top().second;
        que.pop();
        x=u.x,y=u.y,h=u.h;
        d=dis[x][y][h];
        if(x==hom[t1].x&&y==hom[t1].y&&h==0) bz1=1,cost[s][t1]=d;//降到地面才是答案(因为你在云端到达了终点就输出答案的话,你并不知道其他高度的云端是否有更优的结果,所以都要以0的代价降到地面比较结果)
        if(x==hom[t2].x&&y==hom[t2].y&&h==0) bz2=1,cost[s][t2]=d;
        if(bz1&&bz2) return;//优化,找到所有终点就break
        if(h>0)//现在还在云端
            for(int i=0;i<5;++i)//枚举向下走的5条边
            {
                int newx=x+fx[i],newy=y+fy[i];
                if(newx>=1&&newx<=n&&newy>=1&&newy<=m)
                    if(dis[newx][newy][h-1]>d)//和普通dijkstra一样更新答案
                    {
                        dis[newx][newy][h-1]=d;
                        que.push(make_pair(-dis[newx][newy][h-1],(node){newx,newy,h-1}));
                    }
            }
        else//如果在地上,就要弹回云端,消耗一次弹射费用
        {
            h=a[x][y];
            if(dis[x][y][h]>d+b[x][y])//一样的更新答案
            {
                dis[x][y][h]=d+b[x][y];
                que.push(make_pair(-dis[x][y][h],(node){x,y,h}));
            }
        }
    }
}

int main()
{
    freopen("friend.in","r",stdin);
    freopen("friend.out","w",stdout);

    n=getint(),m=getint();
    H=n+m-2;
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            a[i][j]=getint(),a[i][j]=min(a[i][j],H);
    for(int i=1;i<=n;++i)
        for(int j=1;j<=m;++j)
            b[i][j]=getint();
    for(int i=1;i<=3;++i)
        hom[i].x=getint(),hom[i].y=getint(),hom[i].h=a[hom[i].x][hom[i].y];

    for(int i=1;i<=3;++i)
        for(int j=1;j<=3;++j)
            cost[i][j]=1e18;
    dijkstra(1,2,3);//分别跑三个dijkstra求两两最短路
    dijkstra(2,1,3);
    dijkstra(3,1,2);

    long long ans=1e18;char ch;
    if(cost[2][1]+cost[3][1]<ans) ans=cost[2][1]+cost[3][1],ch='X';
    if(cost[1][2]+cost[3][2]<ans) ans=cost[1][2]+cost[3][2],ch='Y';
    if(cost[1][3]+cost[2][3]<ans) ans=cost[1][3]+cost[2][3],ch='Z';
    if(ans==1e18) cout<<"NO"<<'\n';
    else cout<<ch<<'\n'<<ans;
    return 0;
}

本题结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值