• 题解 洛谷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
    ");
    		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
    %d
    ",t,ans);
    	else printf("NO
    ");
    	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=='
    ')
    		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
    "),0;
    	else if (tip==1) printf("X
    ");
    	else if (tip==2) printf("Y
    ");
    	else printf("Z
    ");
    	printf("%d
    ",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;
    }
    

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

    这不算违规小号吧(逃)

  • 相关阅读:
    简单的python购物车
    MS17-010漏洞复现
    记一次简单的sql注入
    利用钟馗之眼对摄像头入侵
    分流抢票软件浅谈
    一次简单的路由器渗透
    运动目标检测中基于HSV空间的阴影去除算法
    RGB颜色空间、HSV颜色空间的理解
    #ifdef __cplusplus extern "C" { #endif 含义
    Opencv 视频保存为图像
  • 原文地址:https://www.cnblogs.com/zhudafu/p/9859945.html
Copyright © 2020-2023  润新知