线段树优化连边

有的时候,

对于一个点,向一段区间内所有点连边

边数是\(O(n^2)\)

复杂度爆炸

于是就有了线段树优化连边......

线段树优化连边,利用到线段树的思想.

对于每个区间,新建一个节点,向子区间递归连边.

这样,当连向一段区间,就等于连向了所有其子节点

十分抽象.

看几道例题

[luogu 5025] [SNOI2017]炸弹

题目链接

题解

暴力做法很显然

连完边后跑\(tarjan + DAG dp\)

然后,线段树优化连边

Code

#include<bits/stdc++.h>

#define LL long long
#define RG register

using namespace std;
template<class T> inline void read(T &x) {
    x = 0; RG char c = getchar(); bool f = 0;
    while (c != '-' && (c < '0' || c > '9')) c = getchar(); if (c == '-') c = getchar(), f = 1;
    while (c >= '0' && c <= '9') x = x*10+c-48, c = getchar();
    x = f ? -x : x;
    return ;
}
template<class T> inline void write(T x) {
    if (!x) {putchar(48);return ;}
    if (x < 0) x = -x, putchar('-');
    int len = -1, z[20]; while (x > 0) z[++len] = x%10, x /= 10;
    for (RG int i = len; i >= 0; i--) putchar(z[i]+48);return ;
}

const int N = 500010, M = 20000010, Mod = 1000000007;

int n, num[N << 2], isleaf[N << 2], tot;

#define ls (rt << 1)
#define rs (rt << 1 | 1)

struct node {
    int to, nxt;
}g[M];
int last[N << 2], gl, L[N], R[N];
void add(int x, int y) {
    g[++gl] = (node) {y, last[x]};
    last[x] = gl;
    return ;
}

struct Segment_Tree {   
    void build(int rt, int l, int r) {
        tot = max(tot, rt);
        if (l == r) {
            isleaf[rt] = 1;
            num[l] = rt;
            return ;
        }
        int mid = (l + r) >> 1;
        add(rt, ls); add(rt, rs);
        build(ls, l, mid), build(rs, mid + 1, r);
        return ;
    }
    void link(int rt, int l, int r, int L, int R, int id) {
        if (L <= l && r <= R) {
            add(id, rt);
            return ;
        }
        int mid = (l + r) >> 1;
        if (L <= mid) link(ls, l, mid, L, R, id);
        if (R > mid) link(rs, mid + 1, r, L, R, id);
        return ;
    }
}T;

LL x[N], y[N];
int dfn[N << 2], low[N << 2], cnt, color[N << 2], C, val[N << 2];
bool in[N << 2];

stack<int> s;

void tarjan(int u) {
    dfn[u] = low[u] = ++cnt;
    in[u] = 1;
    s.push(u);
    for (int i = last[u]; i; i = g[i].nxt) {
        int v = g[i].to;
        if (!dfn[v]) {
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if (in[v]) low[u] = min(low[u], dfn[v]);
    }
    if (dfn[u] == low[u]) {
        C++;
        while (in[u]) {
            color[s.top()] = C;
            val[C] += isleaf[s.top()];
            in[s.top()] = 0;
            s.pop();
        }
    }
    return ;
}

int dp[N << 2];

vector<int> G[N << 2];

inline int dfs(int u) {
    if (dp[u]) return dp[u];
    dp[u] = val[u];
    for (int i = 0; i < (int) G[u].size(); i++) {
        int v = G[u][i];
        dp[u] += dfs(v);
    }
    return dp[u];
}

int main() {
    read(n);
    for (int i = 1; i <= n; i++)
        read(x[i]), read(y[i]);
    for (int i = 1; i <= n; i++) {
        L[i] = lower_bound(x + 1, x + 1 + n, x[i] - y[i]) - x;
        R[i] = upper_bound(x + 1, x + 1 + n, x[i] + y[i]) - x - 1;
    }
    T.build(1, 1, n);
    for (int i = 1; i <= n; i++)
        T.link(1, 1, n, L[i], R[i], num[i]);
    for (int i = 1; i <= tot; i++)
        if (!dfn[i]) tarjan(i);
    for (int i = 1; i <= tot; i++)
        for (int j = last[i]; j; j = g[j].nxt) {
            int v = g[j].to;
            if (color[i] == color[v]) continue;
            G[color[i]].push_back(color[v]);
        }
    for (int i = 1; i <= C; i++) {
        sort(G[i].begin(), G[i].end());
        G[i].erase(unique(G[i].begin(), G[i].end()), G[i].end());
    }
    for (int i = 1; i <= C; i++)
        if (!dp[i])
            dfs(i);
    LL ans = 0;
    for (int i = 1; i <= n; i++)
        (ans += 1ll * i * dp[color[num[i]]]) %= Mod;
    write(ans);
    return 0;
}

看完这题后,应该对于线段树优化连边有了个大概..

然后看一道稍微难一点的.

BZOJ3073 Journeys

权限题警告

题目描述

Seter建造了一个很大的星球,他准备建造N个国家和无数双向道路。N个国家很快建造好了,用1..N编号,但是他发现道路实在太多了,他要一条条建简直是不可能的!于是他以如下方式建造道路:(a,b),(c,d)表示,对于任意两个国家x,y,如果a<=x<=b,c<=y<=d,那么在xy之间建造一条道路。Seter保证一条道路不会修建两次,也保证不会有一个国家与自己之间有道路。 Seter好不容易建好了所有道路,他现在在位于P号的首都。Seter想知道P号国家到任意一个国家最少需要经过几条道路。当然,Seter保证P号国家能到任意一个国家。

注意:可能有重边

题解

这回有两个区间了怎么办?

不能对于(a,b)每个点连区间吧..

我们弄两棵线段树

对于每个点,一棵树管出边,另一棵管入边

线段树里面的边长度都为\(0\)

两棵线段树对应点之间连\(0\)的边

至于为什么要两棵线段树,

想想第一题,我们线段树之中的边只能控制一个方向(如果双向边,那两点距离不就变成\(0\)了)?

因为第一题是点连向区间,所以只要一个线段树来控制区间

然而,这题是区间连向区间

所以我们需要两棵线段树

然后考虑加边,

因为两个区间都被分成了很多段

我们不能每段两两连边

那么我们可以新建一个节点来整合区间

具体实现看代码

Code

#include<bits/stdc++.h>

#define LL long long
#define RG register

using namespace std;
template<class T> inline void read(T &x) {
    x = 0; RG char c = getchar(); bool f = 0;
    while (c != '-' && (c < '0' || c > '9')) c = getchar(); if (c == '-') c = getchar(), f = 1;
    while (c >= '0' && c <= '9') x = x*10+c-48, c = getchar();
    x = f ? -x : x;
    return ;
}
template<class T> inline void write(T x) {
    if (!x) {putchar(48);return ;}
    if (x < 0) x = -x, putchar('-');
    int len = -1, z[20]; while (x > 0) z[++len] = x%10, x /= 10;
    for (RG int i = len; i >= 0; i--) putchar(z[i]+48);return ;
}

int n, m, p, tot;

const int N = 2000010;

struct node {
    int ls, rs;
}t[N << 2];

struct Graph {
    int to, nxt, w;
}g[N * 5];
int last[N << 1], gl, pos[N];
inline void add(int x, int y, int z) {
    g[++gl] = (Graph) {y, last[x], z};
    last[x] = gl;   
}

inline void build1(int &rt, int l, int r) {
    rt = ++tot;
    if (l == r) {
        pos[l] = rt;
        return ;
    }
    int mid = (l + r) >> 1;
    build1(t[rt].ls, l, mid), build1(t[rt].rs, mid + 1, r);
    add(t[rt].ls, rt, 0), add(t[rt].rs, rt, 0);
    return ;
}

inline void build2(int &rt, int l, int r) {
    rt = ++tot;
    if (l == r) {
        add(rt, pos[l], 0);
        return ;
    }
    int mid = (l + r) >> 1;
    build2(t[rt].ls, l, mid), build2(t[rt].rs, mid + 1, r);
    add(rt, t[rt].ls, 0), add(rt, t[rt].rs, 0);
    return ;
}

inline void link1(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) {
        add(rt, tot, 1);
        return ;
    }
    int mid = (l + r) >> 1;
    if (L <= mid)
        link1(t[rt].ls, l, mid, L, R);
    if (R > mid)
        link1(t[rt].rs, mid + 1, r, L, R);
    return ;
}

inline void link2(int rt, int l, int r, int L, int R) {
    if (L <= l && r <= R) {
        add(tot, rt, 1);
        return ;
    }
    int mid = (l + r) >> 1;
    if (L <= mid)
        link2(t[rt].ls, l, mid, L, R);
    if (R > mid)
        link2(t[rt].rs, mid + 1, r, L, R);
    return ;
}

priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > >q;

int dis[N << 1];
bool vis[N << 1];

void dijkstra() {
    memset(dis, 127/3, sizeof(dis));
    dis[pos[p]] = 0;
    q.push(make_pair(0, pos[p]));
    while (!q.empty()) {
        while (vis[q.top().second] && !q.empty()) q.pop();
        if (q.empty()) break;
        int u = q.top().second; q.pop();
        vis[u] = 1;
        for (RG int i = last[u]; i; i = g[i].nxt) {
            int v = g[i].to;
            if (dis[v] > dis[u] + g[i].w) {
                dis[v] = dis[u] + g[i].w;
                q.push(make_pair(dis[v], v));
            }
        }
    }
    return ;
}

int main() {
    read(n), read(m), read(p);
    int rt1, rt2;
    rt1 = rt2 = 0;
    build1(rt1, 1, n); build2(rt2, 1, n);
    while (m--) {
        int a, b, c, d;
        read(a), read(b), read(c), read(d);
        ++tot; link1(rt1, 1, n, a, b); link2(rt2, 1, n, c, d);
        ++tot; link1(rt1, 1, n, c, d); link2(rt2, 1, n, a, b);
    }
    dijkstra();
    for (int i = 1; i <= n; i++)
        printf("%d\n", dis[pos[i]] / 2);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zzy2005/p/10473443.html
今日推荐