[BZOJ4456][Zjoi2016]旅行者(分治+最短路)

Address

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

Solution

考虑借鉴 K-D Tree 的思想,使用分治来做。
首先将询问离线。
当处理 x x 坐标为 [lx,rx] y y 坐标为 [ly,ry] 的子矩形内,对第 lq l q rq r q 个询问操作(这些询问起点都在该子矩形内)时,我们考虑:
如果 ryly>rxlx r y − l y > r x − l x ,那么就令 mid=ly+ry2 m i d = ⌊ l y + r y 2 ⌋
对于所有的 i[lx,rx] i ∈ [ l x , r x ] ,分别以 (i,mid) ( i , m i d ) 为起点,跑单源最短路(注意,这是网格图,需要使用 Dijkstra ,并且跑最短路只能在 x[lx,rx] x ∈ [ l x , r x ] y[ly,ry] y ∈ [ l y , r y ] 范围内的点跑,否则 TLE )。
(i,mid) ( i , m i d ) 为起点跑最短路之后,我们就可以枚举一遍 [lq,rq] [ l q , r q ] 内的所有询问。
dis[x,y] d i s [ x , y ] (i,mid) ( i , m i d ) (x,y) ( x , y ) 的最短路。
如果一个询问是求 (sx,sy) ( s x , s y ) (ex,ey) ( e x , e y ) 的最短路,那么就用 dis[sx,sy]+dis[ex,ey] d i s [ s x , s y ] + d i s [ e x , e y ] 更新该询问的答案。
可以发现,到这里我们对于所有 [lq,rq] [ l q , r q ] 内的询问,
求出了经过了线段 lxxrx,y=mid l x ≤ x ≤ r x , y = m i d 的最短路。
如果不经过这条线段,我们就递归下去,这时候要把 [lq,rq] [ l q , r q ] 内的询问分组。
如果一个询问的起点和终点都在矩形 x[lx,rx],y[ly,mid1] x ∈ [ l x , r x ] , y ∈ [ l y , m i d − 1 ] 内,则分到第一组。
如果一个询问的起点和终点都在矩形 x[lx,rx],y[mid+1,ry] x ∈ [ l x , r x ] , y ∈ [ m i d + 1 , r y ] 内,则分到第二组。
否则分到第三组。
然后对于第一组的询问,递归到子矩形 x[lx,rx],y[ly,mid1] x ∈ [ l x , r x ] , y ∈ [ l y , m i d − 1 ]
对于第二组询问,递归到子矩形 x[lx,rx],y[mid+1,ry] x ∈ [ l x , r x ] , y ∈ [ m i d + 1 , r y ]
如果不满足 ryly>rxlx r y − l y > r x − l x ,那么就令 mid=lx+rx2 m i d = ⌊ l x + r x 2 ⌋
处理线段 x=mid,lyxry x = m i d , l y ≤ x ≤ r y 上的点到其他点的最短路,
向子矩形 x[lx,mid1],y[ly,ry] x ∈ [ l x , m i d − 1 ] , y ∈ [ l y , r y ] x[mid+1,rx],y[ly,ry] x ∈ [ m i d + 1 , r x ] , y ∈ [ l y , r y ] 递归下去。
复杂度分析:
一次分治会导致子矩形大小至少减少一半,故递归深度 O(lognm) O ( log ⁡ n m )
这样,一次询问最多被更新 O(lognm) O ( log ⁡ n m ) 次,这一部分复杂度 O(qlognm) O ( q log ⁡ n m )
我们分治时,由于我们选择了按长边切开,故分治时分割线段的长度应为矩形长和宽的较小值,由于长乘以宽不超过 nm n m ,故长度最长为 O(nm) O ( n m )
我们每次分治时选出了 O(nm) O ( n m ) 个源点跑最短路。
规模为 S S 的图跑 Dijkstra 复杂度 SlogS
由主定理得复杂度 O(nmnmlognm+qlognm) O ( n m n m log ⁡ n m + q log ⁡ n m )

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 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 = 1e5 + 5, M = N << 2, INF = 0x3f3f3f3f;
int n, m, q, ecnt, nxt[M], adj[N], go[M], val[M], dis[N], ans[N],
ox[N], oy[N], vis[N], siv[N], T;
struct cyx {
    int sx, sy, ex, ey, id, res;
} qry[N], q1[N], q2[N], q3[N];
struct pyz {
    int x, y;
    friend inline bool operator < (const pyz &a, const pyz &b) {
        return a.y > b.y;
    }
};
priority_queue<pyz> pq;
inline int which(const int &i, const int &j) {
    return (i - 1) * m + j;
}
void add_edge(int u, int v, int w) {
    nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v; val[ecnt] = w;
    nxt[++ecnt] = adj[v]; adj[v] = ecnt; go[ecnt] = u; val[ecnt] = w;
}
inline void dijk(const int &lx, const int &rx, const int &ly,
const int &ry, const int &lq, const int &rq, const int &S) {
    int i, u;
    while (!pq.empty()) pq.pop(); T++;
    dis[S] = 0; siv[S] = T;
    pq.push((pyz) {S, dis[S]});
    while (!pq.empty()) {
        pyz x = pq.top(); pq.pop();
        if (vis[x.x] == T) continue;
        int u = x.x; vis[u] = T;
        Edge(u) {
            int tx = ox[v], ty = oy[v];
            if (tx < lx || tx > rx || ty < ly || ty > ry)
                continue;
            if (siv[v] < T) siv[v] = T, dis[v] = INF;
            if (dis[u] + val[e] < dis[v])
                dis[v] = dis[u] + val[e], pq.push((pyz) {v, dis[v]});
        }
    }
    For (i, lq, rq) qry[i].res = min(qry[i].res,
        dis[which(qry[i].sx, qry[i].sy)] + dis[which(qry[i].ex, qry[i].ey)]);
}
inline void solve(const int &lx, const int &rx, const int &ly,
const int &ry, const int &lq, const int &rq) {
    if (lx > rx || ly > ry || lq > rq) return;
    int i, t1 = 0, t2 = 0, t3 = 0;
    if (ry - ly > rx - lx) {
        int mid = ly + ry >> 1, tot = lq - 1;
        For (i, lx, rx) dijk(lx, rx, ly, ry, lq, rq, which(i, mid));
        For (i, lq, rq)
            if (qry[i].sy < mid && qry[i].ey < mid)
                q1[++t1] = qry[i];
            else if (qry[i].sy > mid && qry[i].ey > mid)
                q2[++t2] = qry[i];
            else q3[++t3] = qry[i];
        For (i, 1, t1) qry[++tot] = q1[i];
        For (i, 1, t2) qry[++tot] = q2[i];
        For (i, 1, t3) qry[++tot] = q3[i];
        solve(lx, rx, ly, mid - 1, lq, lq + t1 - 1);
        solve(lx, rx, mid + 1, ry, lq + t1, lq + t1 + t2 - 1);
    }
    else {
        int mid = lx + rx >> 1, tot = lq - 1;
        For (i, ly, ry) dijk(lx, rx, ly, ry, lq, rq, which(mid, i));
        For (i, lq, rq)
            if (qry[i].sx < mid && qry[i].ex < mid)
                q1[++t1] = qry[i];
            else if (qry[i].sx > mid && qry[i].ex > mid)
                q2[++t2] = qry[i];
            else q3[++t3] = qry[i];
        For (i, 1, t1) qry[++tot] = q1[i];
        For (i, 1, t2) qry[++tot] = q2[i];
        For (i, 1, t3) qry[++tot] = q3[i];
        solve(lx, mid - 1, ly, ry, lq, lq + t1 - 1);
        solve(mid + 1, rx, ly, ry, lq + t1, lq + t1 + t2 - 1);
    }
}
int main() {
    int i, j, x, y, u, v;
    n = read(); m = read();
    For (i, 1, n) For (j, 1, m - 1)
        x = read(), add_edge(which(i, j), which(i, j + 1), x);
    For (i, 1, n - 1) For (j, 1, m)
        x = read(), add_edge(which(i, j), which(i + 1, j), x);
    For (i, 1, n) For (j, 1, m) u = which(i, j),
        ox[u] = i, oy[u] = j;
    q = read();
    For (i, 1, q) qry[i].sx = read(), qry[i].sy = read(),
        qry[i].ex = read(), qry[i].ey = read(), qry[i].id = i,
        qry[i].res = INF;
    solve(1, n, 1, m, 1, q);
    For (i, 1, q) ans[qry[i].id] = qry[i].res;
    For (i, 1, q) printf("%d\n", ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值