(分块思想、最短路)洛谷 P3645 雅加达的摩天楼
题意
印尼首都雅加达市有 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;
}