双语网站费用地推项目发布平台
题意
印尼首都雅加达市有 N N N 座摩天楼,依次将它们编号为 0 0 0 到 N − 1 N − 1 N−1。
有 M M M 只叫做 “doge” 的神秘生物,编号依次是 0 0 0 到 M − 1 M − 1 M−1。编号为 i i i 的 doge 最初居住于编号为 B i B_i Bi 的摩天楼。每只 doge 都有一种神秘的力量,使它们能够在摩天楼之间跳跃,编号为 i i i 的 doge 的跳跃能力为 P i P_i Pi ( P i > 0 P_i > 0 Pi>0)。
在一次跳跃中,位于摩天楼 b b b 而跳跃能力为 p p p 的 doge 可以跳跃到编号为 b − p b - p b−p (如果 0 ≤ b − p < N 0 \leq b - p < N 0≤b−p<N)或 b + p b + p b+p (如果 0 ≤ b + p < N 0 \leq b + p < N 0≤b+p<N)的摩天楼。
编号为 0 0 0 的 doge 是所有 doge 的首领,它有一条紧急的消息要尽快传送给编号为 1 1 1 的 doge。任何一个收到消息的 doge 有以下两个选择:
- 跳跃到其他摩天楼上;
- 将消息传递给它当前所在的摩天楼上的其他 doge。
请帮助 doge 们计算将消息从 0 0 0 号 doge 传递到 1 1 1 号 doge 所需要的最少总跳跃步数,或者告诉它们消息永远不可能传递到 1 1 1 号 doge。如果消息永远无法传递到 1 1 1 号 doge,输出 − 1 −1 −1。
输入
5 3
0 2
1 1
4 1
输出
5
【样例解释】
下面是一种步数为 5 5 5 的解决方案:
0 0 0 号 doge 跳跃到 2 2 2 号摩天楼,再跳跃到 4 4 4 号摩天楼( 2 2 2 步)。
0 0 0 号 doge 将消息传递给 2 2 2 号 doge。
2 2 2 号 doge 跳跃到 3 3 3 号摩天楼,接着跳跃到 2 2 2 号摩天楼,再跳跃到 1 1 1 号摩天楼( 3 3 3 步)。
2 2 2 号 doge 将消息传递给 1 1 1 号 doge。
1 ≤ N ≤ 30000 1 \leq N \leq 30000 1≤N≤30000, 1 ≤ P i ≤ 30000 1 \leq P_i \leq 30000 1≤Pi≤30000, 2 ≤ M ≤ 30000 2 \leq M \leq 30000 2≤M≤30000。
思路
如此跳跃,不妨看作直线上的“最短路”,一只 doge 跳到另外一只 doge 的摩天楼后,“换乘”另一只 doge 继续跳,那就是 bfs 了。
不难发现,跳跃过程中,最优情况是一只 doge 只使用一次,因为其它 doge 不会跟着 0 0 0 跳,总不能回去找那一只 doge 吧。
造一组数据:
10 3
0 3
5 4
7 3
那么 bfs 时,图大概长这样:
看我暴力 bfs,在洛谷狂砍 95 p t s \textrm{95}pts 95pts。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=3e4+5,inf=0x3f3f3f3f;
ll n,m,b[N],p[N];
vector<ll>a[N];
ll stp[N],ans=inf;
queue<ll>q;
void bfs(ll u,ll step)
{stp[u]=step;while(!q.empty()){ll v=q.front();q.pop();if(v==b[2]){ans=min(ans,step);return; }for(auto x:a[v]){ll t=1;for(int i=v-x;i>=0;i-=x){if(step+t<stp[i]){q.push(i);bfs(i,step+t);}t++;}t=1;for(int i=v+x;i<n;i+=x){if(step+t<stp[i]){q.push(i);bfs(i,step+t);}t++;}}}
}
int main()
{scanf("%lld%lld",&n,&m);for(int i=1;i<=m;i++){scanf("%lld%lld",&b[i],&p[i]);a[b[i]].push_back(p[i]); }memset(stp,inf,sizeof(stp));q.push(b[1]);bfs(b[1],0);if(ans==inf)puts("-1");else printf("%lld",ans);return 0;
}
我们发现一只 doge 的跳跃能力 p p p 比较小的话,那就要建非常多的边,这显然不是我们想要的。
考虑真正使用最短路算法,并且建真正的边,我们尝试在建边上优化。
在 for(int i=b-p;i>=0;i-=p)
和 for(int i=b+p;i<=n;i++)
中,既然容易被卡到 Θ ( n ) \Theta(n) Θ(n),那就只连跳跃能力 p i p_i pi 比较大的 doge。这时候分块思想就派上用场了:
- 若 p > n p>\sqrt{n} p>n,那就按照上图样式建边,建边次数(循环次数)必然小于 n \sqrt{n} n。
其它的怎么办呢?如果我们记录其它的 doge 的具体信息再建边,那和 Θ ( m n ) \Theta(mn) Θ(mn) 没有区别。因此我们用一个标记数组 m a r k \rm mark mark 记录跳跃能力在 [ 1 , n ] [1,\sqrt{n}] [1,n] 的,是否出现过。
记录完之后怎么建这部分图呢?这时候我们考虑枚举 1 ∼ n 1\sim \sqrt{n} 1∼n 的步数 p ′ p' p′,有被打上 m a r k \rm mark mark 标记的,我们就对每个点 i ∈ [ 0 , n − p ′ ) i\in[0,n-p') i∈[0,n−p′),建一条 i ↔ i + p ′ i\leftrightarrow i+p' i↔i+p′ 的、边权为 1 1 1 的双向边,就不从起点开始枚举了。大概长这样:
注意不能再原来的“直线”上直接建,不然本来这个地方没有 doge 在这里你又让他“换乘”。于是考虑对每一个枚举的步数 p ′ p' p′ 建立分层图。那怎么进入分层图呢?我们在输入 b , p b,p b,p 的时候,在起点 b b b,建一个通往 分层图 p p p 所对应分层图编号 b p b_p bp 的、边权为 0 0 0 的入口“单向边”;出去的话,就在分层图上每个对应点,建一个通往原来“直线”的、边权为 0 0 0 的出口单向边,这样就可以实现 doge 的“换乘了”。
然后跑最短路,听说 dijkstra 会超时,那就 spfa 吧。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=3e4+5,M=2e7+9,inf=21e8;
int n,m,B[N];
bool mark[N];
int dis[M];
queue<int>q;
int head[M],idx;
struct edge
{int to,next,w;
}e[M<<1];
void addedge(int u,int v,int w)
{idx++;e[idx].to=v;e[idx].next=head[u];e[idx].w=w;head[u]=idx;
}
bool vis[M];
void bfs(int s)
{q.push(s);dis[s]=0;vis[s]=1;while(!q.empty()){int u=q.front();q.pop();vis[u]=0;for(int i=head[u];i;i=e[i].next){int v=e[i].to,w=e[i].w;if(dis[u]+w<dis[v]){dis[v]=dis[u]+w;if(!vis[v])q.push(v);}}}
}
int fmp(int i,int p)//对应分层图编号:跳的步数 p 在 0~n-1 每个点之间跳跃
{return p*n+i;
}
int main()
{scanf("%d%d",&n,&m);int bSize=min(10,(int)sqrt(n/3));//最优情况时,每只 doge 只会被用 1 次 for(int i=0;i<m;i++){int b,p;scanf("%d%d",&b,&p);B[i]=b;if(p>bSize)//p较大,边数小于n/bSize {int tick=1;for(int j=b-p;j>=0;j-=p){addedge(b,j,tick);tick++;}tick=1;for(int j=b+p;j<n;j+=p){addedge(b,j,tick);tick++;}}else {addedge(b,fmp(b,p),0);//在 b 可以进入步数 p 所对应的分层图 mark[p]=1;}}for(int p=1;p<=bSize;p++)//较小步数 {if(!mark[p])continue;for(int i=0;i+p<n;i++){addedge(fmp(i,p),fmp(i+p,p),1);//在每个步数 p 对应 的分层图上跳跃 addedge(fmp(i+p,p),fmp(i,p),1);}for(int i=0;i<n;i++)//返回上层图 addedge(fmp(i,p),i,0);}for(int i=0;i<M;i++)dis[i]=inf;bfs(B[0]);if(dis[B[1]]>=inf)puts("-1");else printf("%d",dis[B[1]]);return 0;
}