[BZOJ4070][Apio2015]雅加达的摩天楼(建图优化+分块+最短路)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=4070

Solution

我们知道,有一个经典的建图优化:
在最短路等模型中,如果一个 N N 个点的图要对于所有的 ij 之间连边,边权为 |ij| | i − j | ,那么只需要将所有的 i<N i < N 双向边 (i,i+1) ( i , i + 1 ) ,边权为 1 1 即可。
这道题里我们也可以用这样的思想:
对于所有的 0i<M,0j<N,jBi(modPi),jBi ,连有向边 <Bi,j> < B i , j > <script type="math/tex" id="MathJax-Element-5294"> </script> ,边权 |Bij|Pi | B i − j | P i
但这样显然是不行的,为什么呢?
(1) 复杂度不对(废话)
(2)举一例:一只 doge 在 0 0 位置,跳跃能力为 3 ,另一只 doge 在 12 12 位置,跳跃能力为 2 2 ,那么如果按照上面的思想建图,那么 0 位置的 doge 跳到 6 6 之后 12 位置的 doge 就能收到信息,这样显然是步星的。
于是我们要想办法 把每只 doge 的路线独立起来
取一个参数 S S ,新建 N×S 个辅助点,分为 S S 组,第 i 组表示跳跃能力为 i i
对于第 i 组,有一对 1j,kN,j+i=k 1 ≤ j , k ≤ N , j + i = k 则在第 i i 组建双向边 (j,k) ,权值 1 1 ;如果 j 号点初始有 doge 存在,那么连单向边从第 i i 组的第 j 个点到 原图 j j 个点,权值 0
如果跳跃能力大于 S S 则暴力建边。
否则由原图中的 Bi Pi P i 组点内 Bi B i 连权值为 0 0 的有向边。
建图完毕,由 B0 开始跑单源最短路,到 B1 B 1 的最短路为答案。
点数 O(NS) O ( N S ) ,边数 O(N(S+NS)) O ( N ( S + N S ) ) (设 N N M 同阶)
S S min(N,100) 时复杂度比较理想。

Code

#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Step(i, a, b, x) for (i = a; i <= b; i += x)
#define Edge(u) for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
using namespace std;

inline int read()
{
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}

const int N = 3e4 + 5, M = 303e4 + 5, L = 15e6 + 5, INF = 0x3f3f3f3f;

int n, m, B[N], P[N], ecnt, nxt[L], adj[M], go[L], val[L], S, dis[M];
bool vis[M];
queue<int> Q;

void add_edge(int u, int v, int w)
{
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w;
}
int which(int x, int y)
{
    return x * n + y;
}

int SSSP()
{
    memset(dis, INF, sizeof(dis));
    dis[B[1]] = 0; Q.push(B[1]);
    while (!Q.empty())
    {
        int u = Q.front(); Q.pop(); vis[u] = 0;
        Edge(u)
            if (dis[u] + val[e] < dis[v])
            {
                dis[v] = dis[u] + val[e];
                if (!vis[v]) vis[v] = 1, Q.push(v);
            }
    }
    return dis[B[2]];
}

int main()
{
    int i, j, ans;
    n = read(); m = read();
    For (i, 1, m) B[i] = read() + 1, P[i] = read();
    S = min((int) sqrt(n), 100);
    For (i, 1, S) For (j, i + 1, n)
    {
        add_edge(which(i, j - i), which(i, j), 1);
        add_edge(which(i, j), which(i, j - i), 1);
    }
    For (i, 1, S) For (j, 1, n)
        add_edge(which(i, j), j, 0);
    For (i, 1, m)
        if (P[i] <= S)
            add_edge(B[i], which(P[i], B[i]), 0);
        else Step(j, (B[i] - 1) % P[i] + 1, n, P[i])
            if (j != B[i])
                add_edge(B[i], j, abs(B[i] - j) / P[i]);
    if ((ans = SSSP()) == INF) puts("-1");
    else cout << ans << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值