P3645 [APIO2015]雅加达的摩天楼 & ZLOJ练习
written on 2022-06-07
这道题暴力能拿90,然而我比较傻只拿了65。
首先总结一下骗分的经验:因为这题刚开始我是用直接暴力加边的形式来搞的,这样的话要么会RE,要么会MLE。所以骗分的经验就是,如果发觉建边时可能爆空间,就可以采用不建边,在跑最短路的同时松弛能到的点这样的方法。
好了以上都是废话
正解的话,比赛的时候也想到了,这题其实还是蛮显然的。因为 \(\texttt {doge}\) 是以跳的形式来转移的,因此很容易就能想到根号分治。
-
对于 \(p \geq \sqrt n\) 的部分,可以直接建边,这部分复杂度为 \(O(n\sqrt n)\)。
-
对于 \(p \lt \sqrt n\) 的部分,这才是本题的关键。为了解决这一部分,正解是分层图最短路
(真的没想到)。
我们考虑以每一种跳跃能力 \(p\) 作为一层,在每一层内部加边权为 \(1\) 的边,这样的话,这一部分作为预处理的时间复杂度为 \(O(n\sqrt n)\),也就是 \(p\) 的数量乘以点数 \(n\),满足时间复杂度要求。同时,对于每一层的点(也就是这些虚点),向实际对应的点(也就是实点)连边权为 \(0\) 的边,对于这些 \(\texttt {doge}\),向其对应的虚点连上入边。这样一来就能够保证时间复杂度了!
写完题解发现确实对这题的理解,以及对分层图最短路的理解增进了不少。分层图的相邻层之间连的边是那些免费的边,而这一题中,分层图相邻层之间不连边,只是作为一个预处理的过程来优化建边,也就是说分层图只是一个思想而非一个算法,它的拓展是很广泛的。
这题还需要注意一下细节,为了防止爆空间,我们选择把块长调到 \(\min \left(\sqrt n,100 \right)\)。
不知道为什么,这题 \(\texttt {SPFA}\) 好像会比 \(\texttt{Dijkstra}\)要快,然后的话,\(\texttt{SPFA}\) 的 \(vis\) 数组记得每次出队时要清掉,敲代码的时候竟然忘记了。
#include<bits/stdc++.h>
#define N 10000005
#define M 20000005
using namespace std;
int n,m,d[N];
int st,ed;
int tot,tot2,head[N],ver[M],nxt[M],edge[M];
void add_E(int x,int y,int z){ver[++tot]=y,edge[tot]=z,nxt[tot]=head[x],head[x]=tot;}
bool vis[N];
int id(int dep,int x){return dep*n+x;}
queue<int> q;
void SPFA()
{
memset(d,0x3f,sizeof(d));
d[st]=0;
q.push(st);
while(q.size())
{
int x=q.front();
q.pop();
vis[x]=0;
for(int i=head[x];i;i=nxt[i])
{
int y=ver[i],z=edge[i];
if(d[y]>d[x]+z)
{
d[y]=d[x]+z;
if(!vis[y]) vis[y]=1,q.push(y);
}
}
}
}
void build(int x)
{
for(int i=1;i<=n;i++)
{
if(i+x<=n) add_E(id(x,i),id(x,x+i),1),add_E(id(x,x+i),id(x,i),1);
add_E(id(x,i),i,0);
}
}
//id(x,i) refers to the one at i,its ability is x
bool mark[30005];
int main()
{
scanf("%d%d",&n,&m);
int B=min(100,(int)sqrt(n));
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
x++;
if(i==1) st=x;
else if(i==2) ed=x;
if(y>=B)
{
for(int j=x+y,cnt=1;j<=n;j+=y,cnt++) add_E(x,j,cnt);
for(int j=x-y,cnt=1;j>=1;j-=y,cnt++) add_E(x,j,cnt);
}
else
{
add_E(x,id(y,x),0);
if(!mark[y]) build(y),mark[y]=1;
}
}
SPFA();
if(d[ed]>=1e9) puts("-1");
else printf("%d\n",d[ed]);
}