【BZOJ4070】雅加达的摩天楼(APIO2015)-分块+最短路
测试地址:雅加达的摩天楼
题目大意:个点排成一排,有只狗,每只狗有一个跳跃能力,初始它在点,这只狗每步可以往左或往右跳个点。现在要从号狗传信息到号狗,一只狗可以传信息给同个点的其他狗,问最少跳几步。
做法:本题需要用到分块+最短路。
看到这题,第一个想法就是,从每只狗所在的点向所有点连边权为的边,然后跑最短路。然而我们发现,这样建图最多会连出条边,时间和空间上都不能承受。
还有一种想法,就是用类似于网络流的思想,先把每个点拆成个点,这样就形成一个分层图,第层内点和点间连有边权为的双向边,但是为了保证每条路径都是一个合法的方案,我们不能直接在层与层之间连边,而是需要对每个点再拆出一个点,从原点拆出的其他点向这个点连边权为的边,接着,如果点有一条跳跃能力为的狗,就从新拆出的点向原先拆出的第个点连边权为的边。这样就是合法的了,然而边数仍然是的,不能承受。
观察数据范围,容易想到用分块的思想分类讨论,从而把这两种暴力中和成一种更优的做法。在第一种做法中,一只跳跃能力为的狗,能连出条边,而在第二种做法中,每拆出一层的点就要连条边。于是对于的狗,采用第一种做法连边,而对于其它的狗,采用第二种做法连边。第二种做法中最后拆出的一层点和第一种做法中的点重合。这样就最多会连出条边了,于是就可以用SPFA做了。
最后要注意的是,块的大小不能太大,否则会MLE,是合适的。
以下是本人代码(洛谷AC,BZOJ上T了,有待解决):
#include <bits/stdc++.h>
using namespace std;
const int N=30000;
const int inf=1000000000;
int n,m,S,T,blocksiz;
int first[N*200+10]={0},tot=0,dis[N*200+10];
bool vis[N*200+10]={0};
struct edge
{
int v,next,d;
}e[N*500];
queue<int> Q;
int point(int x,int y)
{
return x*(blocksiz+1)+y+1;
}
void insert(int a,int b,int d)
{
e[++tot].v=b;
e[tot].next=first[a];
e[tot].d=d;
first[a]=tot;
}
void init()
{
scanf("%d%d",&n,&m);
blocksiz=min((int)(sqrt(n)+1),100);
for(int i=0;i<m;i++)
{
int b,p;
scanf("%d%d",&b,&p);
if (i==0) S=point(b,0);
if (i==1) T=point(b,0);
if (p>blocksiz)
{
for(int j=1;b+j*p<n;j++)
insert(point(b,0),point(b+j*p,0),j);
for(int j=1;b-j*p>=0;j++)
insert(point(b,0),point(b-j*p,0),j);
}
else insert(point(b,0),point(b,p),0);
}
for(int i=1;i<=blocksiz;i++)
{
for(int j=0;j<n;j++)
insert(point(j,i),point(j,0),0);
for(int j=0;j<i;j++)
{
for(int k=1;j+k*i<n;k++)
{
insert(point(j+(k-1)*i,i),point(j+k*i,i),1);
insert(point(j+k*i,i),point(j+(k-1)*i,i),1);
}
}
}
}
void spfa()
{
for(int i=1;i<=(blocksiz+1)*n;i++)
dis[i]=inf;
dis[S]=0;
Q.push(S);
vis[S]=1;
while(!Q.empty())
{
int v=Q.front();Q.pop();
for(int i=first[v];i;i=e[i].next)
if (dis[v]+e[i].d<dis[e[i].v])
{
dis[e[i].v]=dis[v]+e[i].d;
if (!vis[e[i].v])
{
Q.push(e[i].v);
vis[e[i].v]=1;
}
}
vis[v]=0;
}
if (dis[T]==inf) printf("-1");
else printf("%d\n",dis[T]);
}
int main()
{
init();
spfa();
return 0;
}