题解 洛谷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;
}

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

posted @ 2018-10-27 07:10  泉眼无声惜细流  阅读(162)  评论(1编辑  收藏  举报