题目背景
applepi 想进行宇宙旅行。当然,applepi 知道这是有可能的,因为applepi 的特殊能力能使他观测到宇宙中的虫洞。所谓虫洞就是一个在三维之外的维度打开的快捷通道,通过虫洞能够从一个地方瞬间移动到另外一个地方。
题目描述
为了简化问题,我们建立一个一维坐标系,地球的位置为(0),而applepi 的目的地的位置是一个正整数W。在每一个单位时间里,applepi可以向正方向移动不超过(S) 的一个整数。虫洞可以被表示为二元组((B, E)),即如果在某次移动之后applepi 在位置(B),那么applepi 就会被立刻传送到位置(E)。注意,applepi 在移动过程中如果经过位置(B),由于applepi 的速度极快是不会被传送的。而且,applepi 不能够向负方向移动,但是虫洞引起的除外。现在applepi 想请你帮助他计算一下他至少需要多少个单位时间才能够到达目的地。
输入格式
输入包含多组测试数据。
每组测试数据的第一行是三个正整数 (W,S,P),表示目的地位置,移动限制和虫洞的数目。之后 (P) 行,每行两个整数(B) 和(E),表示一个虫洞。
输入文件的最后一行是一个整数 (0),表示输入的结束。
输出格式
对于每组测试数据,在单独的一行内输出结果。
对于 (100%) 的数据,(Wleq 10^9,2leq Sleq 6,1leq pleq 40),没有(B = 0) 或者(B = W) 的虫洞,输入数据保证目的地可达。
从简单图论那个题单点进来的,本来想A了以后就去做其它图论题单上的题,结果一天就调出来这一个。。。
首先,我觉得这不能是一个绿题,差不多要是蓝,虽然思路不难,但实现需要点功夫,所以,如果管理员看见希望可以考虑一下这个难度是否恰当当然也有可能是我太蒻
首先离散化:
言归正传,观察到,(w)特别大,但(p,s)很小,所以可以考虑从(p,s)入手开始乱搞
建图肯定不能一个数轴点对应一个节点了,考虑到起较大作用的点只有虫洞的两个端点
所以用一下离散化,只保留虫洞入口和出口,当然还要保留起点((0))和终点((w))这样点数是(2p+2),做完离散化后存在(map)数组中
关于离散化,通俗的讲,就是把没用的元素去掉,比如这个题两个虫洞的端点之间的那一堆点,就是没用的,不必保存下来,只保存有用的点(虫洞端点,起点终点),存下他们的坐标就行(存坐标的那个(map)数组的下标也就是建图时的节点编号)
然后还要排个序,方便下面操作
这玩意也听常用,更为深入的自己去百度吧,真的不难
然后想办法建图:
那么如何建图?
首先虫洞的起点和终点肯定是要连一条边权为(0)的边,至于如何通过点的组标确定其对应的节点编号,用二分实现,这是离散化的传统技能
因为我们走路肯定要借助虫洞,不用虫洞走的路就是:从一个虫洞的终点,走到另一个虫洞的起点
因此,我们可以枚举每两个有用的点,计算他们的的距离,如果第一个点的坐标是一个起点,就不计算直接返回无穷大
因为我们不能从一个起点开始走,走到那会接着被传送走,但是我们可以走到一个起点,走到那以后接着会从边长为(0)的虫洞边走到它对应的终点
对于如何判断一个点是否是起点,我用的map<int,int>
,对每个起点打上标记,题解区好多大佬用到是set
,不过本质一样
再考虑非虫洞边的边权咋计算:
设(x,y)分别是要计算的这条边的起点,终点
首先想到的是(lceildfrac{y-x}{s}
ceil)
然而这样写完以后就会像我一样得到10分的好成绩
为什么?因为在([x,y])中,还有一些点是虫洞入口,是不能踩的,一踩就被传送走了
例子楼上有大佬给出了
那我们可以考虑递归的计算(其实一开始想写成非递归,炸了,看来题解就写的递归
先贪心的每步都走(s),看看这样走第一个遇到的虫洞入口是哪个
至于如何实现,肯定就是枚举每个虫洞,都说了p很小
遇到了虫洞入口,就要一格一格往回退,直到遇见了不是虫洞入口的格子
那如果我又退回来起点,说明怎么走都白搭,走不了返回无穷大
没有退回到起点,假设退到(y'),那么从(x)到(y')肯定就是(lceildfrac{y'-x}{s}
ceil)了
那么(y'
ightarrow y)的费用,就可以递归计算了,递归到(x=y),费用肯定为(0),就返回
还有一个细节就是,当遇到了虫洞起点要开始往回退的时候,要判断一下当前这个入口是不是就是(y),如果是,那么说明已经走到了地方,就不退了
最后跑最短路
随便用什么算法跑个最短路就行,范围很小
如果你用dfs就当我没说
最后放上代码,可以从注释中体会一下我调代码时的心情
#include<cstdio>
#include<map>
#include<queue>
#include<cstring>
#include<cmath>
#include<algorithm>
#define reg register
#define EN std::puts("")
inline int read(){
int x=0,y=1;
char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=-1;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return x*y;
}
int n,p,s;
int map[106],tmp[106],tot;
std::map<int,int>start;
int dis[105][105];
int from[105],to[105];
inline int isstart(int x){//判断pos是不是一个起点
return start.find(x)==start.end()?0:1;
}
inline int find(int pos){//这个函数二分查找pos这个点对应的数组下标
reg int l=1,r=tot,mid,ret;
while(l<=r){
mid=(l+r)>>1;
if(map[mid]>=pos) ret=mid,r=mid-1;
else l=mid+1;
}
return ret;
}
inline int getdis(int x,int y){
if(x==y) return 0;
if(isstart(x)) return 0x3f3f3f3f;//x是个起点,不行
int yy=y;
for(reg int i=1;i<=p;i++)if(from[i]>x){
if(yy>from[i]&&(from[i]-x)%s==0) yy=from[i];
}
while(yy!=y&&isstart(yy)) yy--;//如果yy已经等于y了,就算是一个起点也不再退了
if(yy==x) return 0x3f3f3f3f;//退回了起点,永远到不了了qwq
return getdis(yy,y)+std::ceil((double)(yy-x)/s);
}
int main(){for(;;){
n=read();
if(!n) return 0;
s=read();p=read();
tot=0;start.clear();
tmp[++tot]=0;tmp[++tot]=n;
for(reg int i=1;i<=p;i++){
tmp[++tot]=from[i]=read();tmp[++tot]=to[i]=read();
start[from[i]]=1;
// std::printf("%d %d
",start.find(from[i]),start.end());
}
//开始排序和去重
std::sort(tmp+1,tmp+1+tot);
reg int hq=tot;tot=0;
map[++tot]=tmp[1];
for(reg int i=2;i<=hq;i++)if(tmp[i]!=tmp[i-1])
map[++tot]=tmp[i];
//下面开始建边
std::memset(dis,0x3f,sizeof dis);
for(reg int i=1;i<=p;i++) dis[find(from[i])][find(to[i])]=0;
for(reg int i=1;i<=tot;i++)
for(reg int j=i+1;j<=tot;j++)
dis[i][j]=std::min(dis[i][j],getdis(map[i],map[j]));
//floyd
for(reg int k=1;k<=tot;k++)
for(reg int i=1;i<=tot;i++)
for(reg int j=1;j<=tot;j++) dis[i][j]=std::min(dis[i][j],dis[i][k]+dis[k][j]);
std::printf("%d
",dis[1][tot]);
// for(reg int i=1;i<=tot;i++) std::printf("%d ",map[i]);std::puts("");
// for(reg int i=1;i<=p;i++) std::printf("%d %d
",find(from[i]),find(to[i]));std::puts("");
// for(reg int i=1;i<=tot;i++){
// std::printf("%d: ",i);
// for(reg int j=1;j<=tot;j++) std::printf("%d ",dis[i][j]);
// std::puts("");
// }
}
}