题解 P3645 【[APIO2015]雅加达的摩天楼】

题解 P3645 【[APIO2015]雅加达的摩天楼】

一看求最短先想dp,发现要考虑的状态和转移太多没法做。 再一看是从一个点往另一个点跳,可以考虑最短路。

直接建边会O(n^2)

优化:本题中,每个点之间的各种各样。可以先建n个虚图。 其中第i个虚图包含n个点,每个点对应这n座大楼。 这n座大楼任意两个如果相差距离为i,则连边,边长为1.。 但这样还是n^2。

发现当距离大于sqrt(n)时,边就很少了。此时干脆直接去掉sqrt(n)到n的虚图。 这些点的边在原图中暴力连接。

只建立距离<=sqrt(n)的虚图。

总结: 边多则想虚点 先想暴力图,再根据暴力图的条件,设置虚点, 使得实点到该实点的对应虚点距离为0, 虚点间根据条件设计距离,达到减少边的数量的目的。

注意: 有时候要调整分块的大小。例如本题中虚点往: 左右和它距离符合条件的点连边,同时此店还会和原来的实点连边 设t=sqrt(n) 此时已经有有3nt条边。 而对于p[i]>t的情况,有nn/t条边。 所以边数m=3tn+nn/t.(t=sqrt(n)) 发现当n取最大时,仅当t=100时可以使得边的数量最小。 因此blank要取min(100,sqrt(n)),已达到使边的数量尽量小的目的

#include<iostream>
#include<cstdio>
#include<queue>
#include<stack>
#include<algorithm>
#include<cstring>
#include<vector>
#include<ctime>
#include<map>
#include<cstdlib>
#include<cmath>
#define rel(a) a=readl()
#define r(i,a,b) for(int i=a;i<=b;i++)
#define rr(i,a,b) for(int i=a;i>=b;i--)
#define inf 0x3f3f3f3f
#define mem(a) memset(a,0x3f,sizeof(a))
#define re(a) a=read()
#define pr(a) printf("%d\n",a)
#define me(a) memset(a,0,sizeof(a))
#define in inline
#define ll long long
#define db double
#define id(i,j) (n*j)+i
using namespace std;
const int N=30007;
const int M=207;
inline int read(){
    char ch=getchar();
    int w=1,x=0;
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0',ch=getchar();}
    return x*w;
}
int n,m,head[N*300],b[N],p[N],blank,cnt=0,vis[N*300],d[N*300];
struct edge{
    int v,nxt,w;
}e[N*600];
in void add(int u,int v,int w){
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].nxt=head[u];
    head[u]=cnt;
}
queue<int> q;
in void bfs(int S){
    mem(d),me(vis);
    q.push(S),vis[S]=1,d[S]=0;
    int u,v,w;
    while(!q.empty()){
        u=q.front(),v,w;
        vis[u]=0;
        q.pop();
        for(int i=head[u];i;i=e[i].nxt){
            v=e[i].v,w=e[i].w;
            if(d[v]>d[u]+w){
                d[v]=d[u]+w;
                if(!vis[v]){
                    q.push(v);vis[v]=1;
                }
            }
        }
    }
}
int main(){
    re(n),re(m);
    blank=min((int)sqrt(n),100);
    r(i,1,blank){
        r(j,0,n-i-1){
            add(id(j,i),id(j+i,i),1);
            add(id(j+i,i),id(j,i),1);
        }
        r(j,0,n-1)add(id(j,i),j,0);
    }
    r(i,0,m-1){
        re(b[i]),re(p[i]);
        if(p[i]<=blank)add(b[i],id(b[i],p[i]),0);
        else{
            for(int j=b[i]-p[i];j>=0;j-=p[i])add(b[i],j,(b[i]-j)/p[i]);
            for(int j=b[i]+p[i];j<=n-1;j+=p[i])add(b[i],j,(j-b[i])/p[i]);
        }
    }
    bfs(b[0]);
    printf("%d\n",d[b[1]]<inf?d[b[1]]:-1);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值