首页 > 其他 > 详细

[SNOI2017]炸弹(线段树优化建图)

时间:2020-05-10 00:15:35      阅读:68      评论:0      收藏:0      [点我收藏+]

题目链接

技术分享图片

 

很直观的想法是直接把每个点向能炸到的点都连一条边,然后缩点dag再dp一下求出每个点能炸到的最左端和最右端。

可是这样空间and时间复杂度都爆炸,直接gg。

怎么优化它呢?

我们发现每个点能引爆的点一定在某个区间内,所以我们容懿想到线段树优化建图。

建一棵线段树,从父节点连向它的两个子节点一条有向边。

对于每个节点,计算出它自己爆炸的范围,然后向线段树上代表这个区间的那些点连边。

这个节点其实用线段树上的叶节点即可,不用再建新的节点。

然后跑一遍tarjan强连通分量缩点,每个新点要记录改分量里能炸到的最远的左右端点。

之后我们还有跑一点dag dp,用每个节点能到达的点更新其左右端点。

注意这里不能直接拓扑,要在反图上拓扑,因为这里直接拓扑时孩子是没有更新的(虽然能过。。。)

查询时直接返回对应scc的左右端点的差即可。

上代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
typedef long long ll;
const ll N = 5000010;
const ll mod = 1e9 + 7;
struct bomb{
    ll pos, r;
}a[N];
ll id[N];
ll n;
ll dfn[N], dep, low[N], stk[N], top;
ll col[N], scc;
ll mx;
bool vis[N];
ll L[N], R[N], Left[N], Right[N], ans;
vector<ll> vec[N], G[N];
void add(int u, int v) {
    G[u].push_back(v);
}
void build(ll p, ll l, ll r) {
    mx = max(mx, p);
    L[p] = l;
    R[p] = r;
    if (l == r) {
        id[l] = p;
        return;
    }
    ll mid = (l + r) >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    add(p, p << 1);
    add(p, p << 1 | 1);
}
void work(ll p, ll l, ll r, ll x, ll y, ll v) {
    if (r < x || l > y) return;
    if (x <= l && r <= y) {
        if (v == p) return;
        add(v, p);
        return;
    }
    ll mid = (l + r) >> 1;
    work(p << 1, l, mid, x, y, v);
    work(p << 1 | 1, mid + 1, r, x, y, v); 
}
void tarjan(ll x) {
    dfn[x] = low[x] = ++dep;
    stk[++top] = x;
    vis[x] = 1;
    for (ll i = 0; i < G[x].size(); i++) {
        ll y = G[x][i];
        if (!dfn[y]) {
            tarjan(y);
            low[x] = min(low[x], low[y]);
        } else if (vis[y]) {
            low[x] = min(low[x], dfn[y]);
        }
    }
    if (dfn[x] == low[x]) {
        col[x] = ++scc;
        vis[x] = 0;
        Left[scc] = L[x];
        Right[scc] = R[x];
        while (stk[top] != x) {
            Left[scc] = min(Left[scc], L[stk[top]]);
            Right[scc] = max(Right[scc], R[stk[top]]);
            col[stk[top]] = scc;
            vis[stk[top]] = 0;
            top--;
        }
        top--;
    }
}
ll Find1(ll l, ll r, ll v) {
    ll ret = 0;
    while (l <= r) {
        ll mid = (l + r) >> 1;
        if (a[mid].pos <= v) {
            ret = mid;
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
    return ret;
}
ll Find2(ll l, ll r, ll v) {
    ll ret = 0;
    while (l <= r) {
        ll mid = (l + r) >> 1;
        if (a[mid].pos >= v) {
            ret = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    return ret;
}
void dfs(int x) {
    vis[x] = 1;
    for (int i = 0; i < vec[x].size(); i++) {
        int y = vec[x][i];
        if (vis[y]) {
            Left[x] = min(Left[x], Left[y]);
            Right[x] = max(Right[x], Right[y]);
            continue;
        }
        dfs(y);
        Left[x] = min(Left[x], Left[y]);
        Right[x] = max(Right[x], Right[y]);
    }
}
ll query(ll x) {
    return Right[x] - Left[x] + 1;
}
bool cmp(bomb p, bomb q) {
    return p.pos < q.pos;
}
int main() {
    scanf("%lld", &n);
    for (ll i = 1; i <= n; i++) {
        scanf("%lld%lld", &a[i].pos, &a[i].r);
    }
    sort(a + 1, a + 1 + n, cmp);
    build(1, 1, n);
    for (ll i = 1; i <= n; i++) {
        if (a[i].r == 0) continue;
        ll r = Find1(i, n, a[i].pos + a[i].r);
        ll l = Find2(1, i, a[i].pos - a[i].r);
        work(1, 1, n, l, r, id[i]);
        Left[id[i]] = l;
        Right[id[i]] = r;
    }
    tarjan(1);
    for (ll x = 1; x <= mx; x++) {
        for (ll j = 0; j < G[x].size(); j++) {
            ll y = G[x][j];
            if (col[x] != col[y]) {
                vec[col[x]].push_back(col[y]);
            }
        }
    }
    for (int i = 1; i <= scc; i++) {
        sort(vec[i].begin(), vec[i].end());
        unique(vec[i].begin(), vec[i].end());
    }
    memset(vis, 0, sizeof(vis));
    for (int i = 1; i <= scc; i++) {
        if (!vis[i]) dfs(i);
    }
    for (ll i = 1; i <= n; i++){
        ans += (i * query(col[id[i]])) % mod;
        ans %= mod;
    }
    cout << ans % mod;
    return 0;
}

 

[SNOI2017]炸弹(线段树优化建图)

原文:https://www.cnblogs.com/zcr-blog/p/12860496.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!