题解 洛谷P4473 【[国家集训队]飞飞侠】

这道题今天我们考试考到了,第三题,最后只剩半小时了,随便打了个暴搜,最后竟然还没调完QAQ,我竟然连暴力都不会打了

咳咳,不扯了,下面开始说这道题的做法

由于N和M都不大于150最容易想到的是Floyd(其实是Dijkstra不会写)于是就有了复杂度为O($n^6$)的25分算法

由此,我们可以得到以下的25分做法(考试时25分,洛谷嘛......0分)
代码如下(机房某个大佬写的):

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define rep(i,a,b) for (register int i(a);i<=(b);i++)
using namespace std;
template <typename T>
int read(T &x) {
    x=0;int f=1;char c=getchar();
    for(; !isdigit(c); c=getchar()) if(c=='-') f=-f;
    for(; isdigit(c); c=getchar()) x=x*10+c-'0';
    x*=f;
}
int ans,n,m,b[20][20],a[20][20],c[20][20][20][20],x1,x2,x3,z1,z2,z3;
char t;
int main(){
    freopen("zhber.in","r",stdin);
    freopen("zhber.out","w",stdout);
    read(n),read(m);
    if (n*m>450){
        printf("NO\n");
        return 0;
    }
    memset(b,0,sizeof b);
    memset(a,0,sizeof a);
    memset(c,0,sizeof c);
    rep(i,1,n)rep(j,1,m) read(b[i][j]);
    rep(i,1,n)rep(j,1,m) read(a[i][j]);
    read(x1),read(z1),read(x2),read(z2),read(x3),read(z3);
    rep(i1,1,n)rep(j1,1,m)rep(i2,1,n)rep(j2,1,m)
        if ((i1==i2)&&(j1==j2)) c[i1][j1][i1][j1]=0;
        else{
            int d=abs(i1-i2)+abs(j1-j2);
            if (b[i1][j1]>=d) c[i1][j1][i2][j2]=a[i1][j1];
            else c[i1][j1][i2][j2]=INF;
            if (b[i2][j2]>=d) c[i2][j2][i1][j1]=a[i2][j2];
            else c[i2][j2][i1][j1]=INF;
        }
    rep(i1,1,n)rep(j1,1,m)rep(i2,1,n)rep(j2,1,m)rep(i3,1,n)rep(j3,1,m)
        if (c[i2][j2][i3][j3]>(c[i2][j2][i1][j1]+c[i1][j1][i3][j3])) c[i2][j2][i3][j3]=c[i2][j2][i1][j1]+c[i1][j1][i3][j3];
    ans=INF;
    if (ans>c[x1][z1][x2][z2]+c[x3][z3][x2][z2]) ans=c[x1][z1][x2][z2]+c[x3][z3][x2][z2],t='X';
    if (ans>c[x2][z2][x1][z1]+c[x3][z3][x1][z1]) ans=c[x2][z2][x1][z1]+c[x3][z3][x1][z1],t='Y';
    if (ans>c[x1][z1][x3][z3]+c[x2][z2][x3][z3]) ans=c[x1][z1][x3][z3]+c[x2][z2][x3][z3],t='Z';
    if (ans<INF) printf("%c\n%d\n",t,ans);
    else printf("NO\n");
    return 0;
}

emmmmmm,其实我就是凑字数

看一下没什么不好2333,下面有好东西,Be patient~

好了,言归正传。

首先,我们注意到这是一个最短路问题,那么自然而然就想到了Dijkstra算法(不要管SPFA,她已经死了)。但是本题和纯的单元最短路径略有不同,需要转化一下。

那么,最容易想到的是在每个点和它可以到达的点之间加一条边,然而,这样一来,边总数的最大值是$150^4$$*2=1012500000

自然,这是存不下的(洛谷上空间给了512MB我们考试才给128MB),所以不建立边,直接把在某个点弹射范围内的点扫一遍,然后全部压到队列里去,这样就可以写出这样的代码(复杂度:O($(nm)^2$log(n$$m))):

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define REP(i,a,b) for (register int i(a);i<=(b);i++)
using namespace std;
int d,Da,Ans,n,m,Dist[160][160],x[5],y[5],p[5][5],a[160][160],b[160][160];
struct Node {
    int x,y,q;
    Node(int xx,int yy,int qq) {
        x=xx;
        y=yy;
        q=qq;
    }
};
bool operator < (Node a,Node b) {
    return a.q>b.q;
}
template <typename T>
int Read(T &x) {
    x=0;
    int f=1;
    char c=getchar();
    while(c!='-'&&c>'9'&&c<'0')
        c=getchar();
    for(; !isdigit(c); c=getchar())
        if(c=='-')
            f=-f;
    for(; isdigit(c); c=getchar())
        x=x*10+c-'0';
    x*=f;
    if(c=='\n')
        return 1;
    else
        return 0;
}
void Write(long long x) {
    if(x<0) {
        putchar('-');
        x=-x;
    }
    if(x>9) {
        Write((x-x%10)/10);
    }
    putchar(x%10+'0');
}
inline void Dijkstra(int k) {
    REP(i,1,n) {
        REP(j,1,m) {
            Dist[i][j]=INF;
        }
    }
    priority_queue<Node> Q;
    Q.push(Node(x[k],y[k],0));
    Dist[x[k]][y[k]]=0;
    while(!Q.empty()) {
        Node X=Q.top();
        Q.pop();
        if(Dist[X.x][X.y]!=X.q) {
            continue;
        }
        int Len=b[X.x][X.y],v=Dist[X.x][X.y]+a[X.x][X.y];
        REP(i,max(1,X.x-Len),min(n,X.x+Len)) {
            int Tmp=Len-abs(X.x-i);
            REP(j,max(1,X.y-Tmp),min(m,X.y+Tmp)) {
                if(Dist[i][j]>v) {
                    Dist[i][j]=v;
                    Q.push(Node(i,j,Dist[i][j]));
                }
            }
        }
    }
    REP(i,1,3) {
        p[k][i]=Dist[x[i]][y[i]];
    }
}
int main() {
    Read(n);
    Read(m);
    REP(i,1,n) {
        REP(j,1,m) {
            Read(b[i][j]);
        }
    }
    REP(i,1,n) {
        REP(j,1,m) {
            Read(a[i][j]);
        }
    }
    REP(i,1,3) {
        Read(x[i]);
        Read(y[i]);
    }
    REP(i,1,3) {
        Dijkstra(i);
    }
    int Da=0,Ans=INF;
    REP(i,1,3) {
        d=0;
        d=p[1][i]+p[2][i]+p[3][i];
        if(d<Ans) {
            Da=i;
            Ans=d;
        }
    }
    if(Ans==INF) {
        puts("NO");
    } else {
        switch(Da) {
            case 0: {
                puts("NO");
                break;
            }
            case 1: {
                puts("X");
                Write(Ans);
                break;
            }
            case 2: {
                puts("Y");
                Write(Ans);
                break;
            }
            case 3: {
                puts("Z");
                Write(Ans);
                break;
            }
        }
    }
    return 0;
}

这个代码在洛谷上可以AC,但考试时只有60分(因为洛谷时限时5000ms,我们学校的是1000ms)QAQ

所以还要优化,于是有人写出了线段树优化Dijkstra(复杂度:我不会算啊QAQ,哪个大佬教教我)

代码我懒得写了看一下下面大佬的题解吧

当然,还有我们机房的一个神仙写出了二维线段树(复杂度我同样不会算QAQ,但是似乎比一般线段树快不少呢)
(代码来自Centaurus99巨佬%%%%)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
inline int read(){
    int x=0,f=1;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return x*f;
}
const int N=160,INF=0x3f3f3f3f;
int n,m,a[N][N],b[N][N],tag[N][N];
struct node{
    int v,x;
};
inline bool operator<(const node&t1,const node&t2){return t1.v<t2.v||(t1.v==t2.v&&t1.x>t2.x);}
inline void inc(node&x,node&v){x=(v<x?v:x);}
inline void inc(int&x,int&v){x=(v<x?v:x);}
struct DATA{
    node mn;
    int addv;
};
struct SGT{
    DATA T[N<<2];
    inline void create(int o,int l,int r){
        T[o].mn.v=INF,T[o].addv=INF;
        if (l==r){
            T[o].mn.x=l;
            return;
        }
        int mid=(l+r)>>1;
        create(o<<1,l,mid),create(o<<1|1,mid+1,r);
        pushup(o);
    }
    inline void pushup(int o){T[o].mn=min(T[o<<1].mn,T[o<<1|1].mn);}
    inline void pushdown(int o){
        if (T[o].addv!=INF){
            if (T[o<<1].mn.x) inc(T[o<<1].mn.v,T[o].addv),inc(T[o<<1].addv,T[o].addv);
            if (T[o<<1|1].mn.x) inc(T[o<<1|1].mn.v,T[o].addv),inc(T[o<<1|1].addv,T[o].addv);
            T[o].addv=INF;
        }
    }
    inline void qadd(int o,int l,int r,int x,int y,int v){
        if (T[o].mn.x==0) return;
        if (x<=l&&r<=y){
            inc(T[o].mn.v,v),inc(T[o].addv,v);
            return;
        }
        pushdown(o);
        int mid=(l+r)>>1;
        if (y<=mid) qadd(o<<1,l,mid,x,y,v);
        else if (x>mid) qadd(o<<1|1,mid+1,r,x,y,v);
        else qadd(o<<1,l,mid,x,y,v),qadd(o<<1|1,mid+1,r,x,y,v);
        pushup(o);
    }
    inline void qset(int o,int l,int r,int x,int v){
        if (l==r){
            T[o].mn.v=v;
            if (v==INF) T[o].mn.x=0;
            return;
        }
        pushdown(o);
        int mid=(l+r)>>1;
        if (x<=mid) qset(o<<1,l,mid,x,v);
        else qset(o<<1|1,mid+1,r,x,v);
        pushup(o);
    }
}T[N];
struct PLA{
    int x,y;
}S[4];
int G[4][4];
inline void update(int x,int y,int v){
    int lim=min(n,x+b[x][y]);
    for (int i=max(1,x-b[x][y]);i<=lim;++i){
        int l=max(1,y-b[x][y]+abs(i-x)),r=min(m,y+b[x][y]-abs(i-x));
        T[i].qadd(1,1,m,l,r,v+a[x][y]);
    }
}
void dij(int s){
    int t1,t2;
    if (s==1) t1=2,t2=3;
    else if (s==2) t1=1,t2=3;
    else t1=1,t2=2;
    for (int i=1;i<=n;++i) T[i].create(1,1,m);
    T[S[s].x].qset(1,1,m,S[s].y,0);
    while (G[s][t1]==-1||G[s][t2]==-1){
        node u;u.v=INF;int ux=0;
        for (int i=1;i<=n;++i){
            node t=T[i].T[1].mn;
            if (t<u) u=t,ux=i;
        }
        if (u.x==0||ux==0) break;
        if (ux==S[t1].x&&u.x==S[t1].y) G[s][t1]=u.v;
        if (ux==S[t2].x&&u.x==S[t2].y) G[s][t2]=u.v;
        T[ux].qset(1,1,m,u.x,INF);
        update(ux,u.x,u.v);
    }
}
int main(){
    n=read(),m=read();
    for (int i=1;i<=n;++i){
        for (int j=1;j<=m;++j) b[i][j]=read();
    }
    for (int i=1;i<=n;++i){
        for (int j=1;j<=m;++j) a[i][j]=read();
    }
    for (int i=1;i<=3;++i) S[i].x=read(),S[i].y=read(),tag[S[i].x][S[i].y]=i;
    memset(G,-1,sizeof(G));
    int tans=INF,tip=0;
    for (int i=1;i<=3;++i) dij(i);
    for (int i=1;i<=3;++i){
        int now=0;
        for (int j=1;j<=3;++j)if(i!=j){
            if (G[j][i]==-1){
                now=INF;
                break;
            }
            now+=G[j][i];
        }
        if (now<tans) tans=now,tip=i;
    }
    if (tip==0) return printf("NO\n"),0;
    else if (tip==1) printf("X\n");
    else if (tip==2) printf("Y\n");
    else printf("Z\n");
    printf("%d\n",tans);
    return 0;
}

当然,也有人写分层图Dijkstra的,题解里有人发了,我就不写了(其实是懒得写2333)

下面重点来了

敲黑板~

上面的算法都比较优秀的解决了这道题(尤其是后面2种),但是......

还不够快

下面介绍一种Dijkstra+并查集优化的算法,这是我在网上看到的,原帖并没有讲解,那我来说一下吧

本题中,每一个点都被扫到很多次,如果当这个点不再被需要更新时跳过这个点,就可以节省很多时间,这个算法就是基于这样一种思想。

这道题中,经过仔(yi)细(dun)观(luan)察(gao)我们可以发现,如果把点压入堆中时,比较Dist[i]+w[i]而不是比较Dist[i],就可以保证已经更新过的的点以后不再需要更新。
证明(我这么菜,当然不会证了QAQ,以下证明是机房里zcyskyHolyk两位巨佬在一个月黑风高的夜晚想出来的,在此引用一下,希望他们别打我):

假设点 x 已经得到了最短路,证明用该点更新的 y也得到了最短路

反证,假设存在路径 x′→y

使 dis[y] 更小,且在 x 更新 y 之后,那么有 dis[x′]+w[x]<dis[x]+w[x] ,因为 x′ 在 x 之后,有 dis[x′]+w[x′]≥dis[x]+w[x],两式矛盾,运用数学归纳法,可知上述结论成立,以及起点 s 到每一点的最短路径就是 dis[i]

于是我们可以每一行建立一个并查集来跳过已经松弛过的节点,这样代码效率就会大大提高(复杂度:$O(nm)log(n*m)$)
代码如下:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define REP(i,a,b) for (register int i(a);i<=(b);i++)
namespace fast_IO {
    const int IN_LEN=10000000,OUT_LEN=10000000;
    char ibuf[IN_LEN],obuf[OUT_LEN],*ih=ibuf+IN_LEN,*oh=obuf;
    char *lastin=ibuf+IN_LEN;
    const char *lastout=ibuf+OUT_LEN-1;
    inline char getchar_() {
        if(ih==lastin)lastin=ibuf+fread(ibuf,1,IN_LEN,stdin),ih=ibuf;
        return (*ih++);
    } inline void putchar_(const char x) {
        if(ih==lastout)fwrite(obuf,1,oh-obuf,stdout),oh=obuf;
        *oh++=x;
    } inline void flush() {
        fwrite(obuf, 1, oh - obuf, stdout);
    }
}
using namespace fast_IO;
template <typename T>
inline void Read(T&x) {
    char cu=getchar();
    x=0;
    bool fla=0;
    while(!isdigit(cu)) {
        if(cu=='-')fla=1;
        cu=getchar();
    }
    while(isdigit(cu))x=x*10+cu-'0',cu=getchar();
    if(fla)x=-x;
}
template <typename T>
void printe(const T x) {
    if(x>=10)printe(x/10);
    putchar(x%10+'0');
}
template <typename T>
inline void Write(const T x) {
    if(x<0)putchar('-'),printe(-x);
    else printe(x);
}
//玄学的读入优化
using namespace std;
int n,m,Res,Da,x[5],y[5],a[160][160],b[160][160],Top[160][160];//Top是并查集
long long Dist[160][160],Ans[5];//Dist数组存离原点的距离,Ans数组存当到前点会合的最小代价
struct Node {
    long long Dis;
    int x,y;
    Node() {} Node(int x,int y,long long Dis):Dis(Dis),x(x),y(y) {}
};
bool operator <(Node a,Node b) {
    return a.Dis>b.Dis;
}
int T(int Top[],int x) {
    return Top[x]==x ? x:(Top[x]=T(Top,Top[x]));
}//并查集,用来跳过已经松弛过的节点
void Dijkstra(int k) {//Dijkstra查询最短路
    Node X;
    int L,R,Nx,Ny,Len1,Len2;
    REP(i,1,n) {
        REP(j,1,m+1) {
            Dist[i][j]=INF;
            Top[i][j]=j;
        }
    }//初始化
    priority_queue<Node>Q;
    Q.push(Node(x[k],y[k],a[x[k]][y[k]]));
    Top[x[k]][y[k]]=y[k]+1;
    Dist[x[k]][y[k]]=0;//把第一个点压进队列,并把它的下一个设为设为它右边的点
    while(!Q.empty()) { //和正常的Dijkstra一样
        X=Q.top();
        Q.pop();//取出队头
        Len1=b[X.x][X.y];
        Nx=max(1,X.x-Len1);
        Ny=min(n,X.x+Len1);//设置X能弹射的范围
        REP(i,Nx,Ny) {
            Len2=Len1-abs(X.x-i);
            L=max(1,X.y-Len2);
            R=min(m,X.y+Len2);
            for(int j=T(Top[i],L); j<=R; j=T(Top[i],j)) {//并查集跳过已经松弛的点
                Q.push(Node(i,j,X.Dis+a[i][j]));//把可以跳到并且没有被松弛过的点压进队列
                Dist[i][j]=X.Dis;//松弛操作
                Top[i][j]=j+1;//把这个点的下一个设置为它右边的点
            }
        }
    }
}
int main() {
    Read(n);
    Read(m);
    REP(i,1,n) {
        REP(j,1,m) {
            Read(b[i][j]);
        }
    }
    REP(i,1,n) {
        REP(j,1,m) {
            Read(a[i][j]);
        }
    }
    REP(i,1,3) {
        Read(x[i]);
        Read(y[i]);
    }
    fill(Ans,Ans+3,0);
    Res=INF;
    REP(i,1,3) {
        Dijkstra(i);
        REP(j,1,3) {
            Ans[j]+=Dist[x[j]][y[j]]; //分别算出到每个点会合的最小代价
        }
    }
    REP(i,1,3) {
        if(Ans[i]<Res) {//打擂台找出最优解
            Res=Ans[i];
            Da=i;
        }
    }
    if(Res==INF) {//按题目要求输出答案
        puts("NO");
    } else {
        switch(Da) {
            case 1: {
                puts("X");
                break;
            }
            case 2: {
                puts("Y");
                break;
            }
            case 3: {
                puts("Z");
                break;
            }
        }
        Write(Res);
    }
    getchar();
    return flush(),0;
}

提交记录(小号)速度确实快了不少呢

这不算违规小号吧(逃)

转载于:https://www.cnblogs.com/zhudafu/p/9859945.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值