这道题今天我们考试考到了,第三题,最后只剩半小时了,随便打了个暴搜,最后竟然还没调完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,以下证明是机房里zcysky和Holyk两位巨佬在一个月黑风高的夜晚想出来的,在此引用一下,希望他们别打我):
假设点 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;
}
提交记录(小号)速度确实快了不少呢
这不算违规小号吧(逃)