SNOI2017 炸弹

题目链接

Solution

如果把每个炸弹看成点,将它向所有它能引爆的炸弹连有向边,那么图中同一个强连通分量就代表其中所有的炸弹可以相互引爆。这样我们对连边之后的图进行缩点,就能获得一个 DAG。

可以发现,缩点之后的每个点都代表了一段炸弹区间;而每个点最终能引爆的炸弹也形成一段区间。

因此,我们可以在 tarjan 的过程中记下每个点表示的左端点(Minl)和右端点(Maxr),然后反向建图进行拓扑排序,若 v->u,用 \(Minl_v\) 来更新 \(Minl_u\),用 \(Maxr_v\) 来更新 \(Maxr_u\),最终求得每个点实际能引爆的区间。然后我们就能知道每个炸弹能引爆的炸弹数,统计答案即可。

但是数据范围 \(5 * 10^5\),暴力建图的时间和空间复杂度都过高。考虑到是要向区间连边,可以使用线段树优化建图。如下图:

(线段树上每个节点向它的两个儿子连边)

如果我们要 2 节点向 [4,7] 连边,就可以采取如上方式,让 2->4,2->6,2->7。这个例子的优化不是很明显,但是一旦数据增大,可以将建图复杂度优化到 \(\log\) 级别。

可以看出,线段树优化建图跟线段树的关系不大,只是采取了线段树的形式。

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define LL long long
using namespace std;

const int N = 666666, mod = 1e9 + 7;
struct edge { LL nxt, to, f; } e[N << 3], f[N << 3];
LL n, id = 0, top = 0, cnt = 0, cnt2 = 0, Ans = 0, color = 0;
LL st[N << 2], du[N << 2], vis[N << 2], dfn[N << 2], low[N << 2], col[N << 2], head[N << 2];
LL ind[N], h2[N << 2], Maxr[N << 2], x[N], r[N], Minl[N << 2];

void add(LL x, LL y) { e[++cnt] = (edge) { head[x], y, x }, head[x] = cnt; }
void add2(LL x, LL y) { f[++cnt2] = (edge) { h2[x], y, x }, h2[x] = cnt2; }

void build(LL x, LL l, LL r)
{
    if(l == r)
    {
        ind[l] = x;
        return ;
    }
    LL mid = (l + r) >> 1;
    build(x << 1, l, mid);
    build(x << 1 | 1, mid + 1, r);
    add(x, x << 1);
    add(x, x << 1 | 1);
}
void update(LL x, LL l, LL r, LL stdl, LL stdr, LL std)
{
    if(l > stdr || r < stdl) return ;
    if(stdl <= l && stdr >= r)
    {
        if(x != std) add(std, x);
        return ;
    }
    LL mid = (l + r) >> 1;
    update(x << 1, l, mid, stdl, stdr, std);
    update(x << 1 | 1, mid + 1, r, stdl, stdr, std);
}

void tarjan(LL x)
{
    dfn[x] = low[x] = ++id;
    st[++top] = x, vis[x] = 1;
    for(LL i = head[x]; i; i = e[i].nxt)
    {
        LL v = e[i].to;
        if(!dfn[v]) tarjan(v), low[x] = min(low[x], low[v]);
        else if(vis[v]) low[x] = min(low[x], dfn[v]);
    }
    if(dfn[x] == low[x])
    {
        color++, st[top + 1] = -1;
        while(st[top + 1] != x)
            vis[st[top]] = 0, col[st[top--]] = color;
    }
}
void top_sort()
{
    queue <int> q; 
    for(LL i = 1; i <= color; i++) if(du[i] == 0) q.push(i);
    while(!q.empty())
    {
        LL a = q.front();
        q.pop();
        for(LL i = h2[a]; i; i = f[i].nxt)
        {
            LL v = f[i].to;
            Minl[v] = min(Minl[v], Minl[a]);
            Maxr[v] = max(Maxr[v], Maxr[a]);
            du[v]--;
            if(du[v] == 0) q.push(v);
        }
    } 
}

void Clear()
{
    memset(du, 0, sizeof(du));
    memset(h2, 0, sizeof(h2));
    memset(vis, 0, sizeof(vis));
    memset(low, 0, sizeof(low));
    memset(dfn, 0, sizeof(dfn));
    memset(head, 0, sizeof(head));
    memset(Maxr, 0, sizeof(Maxr));
    memset(Minl, 0x3f, sizeof(Minl));
    return ;
}

int main()
{
    scanf("%lld", &n);
    Clear(), build(1, 1, n);
    for(LL i = 1; i <= n; i++) scanf("%lld%lld", &x[i], &r[i]);
    for(LL i = 1; i <= n; i++)
    {
        LL il = lower_bound(x + 1, x + n + 1, x[i] - r[i]) - x;
        LL ir = upper_bound(x + 1, x + n + 1, x[i] + r[i]) - x - 1;
        update(1, 1, n, il, ir, ind[i]);
    }
    tarjan(1);
    for(LL i = 1; i <= n; i++)
    {
        Minl[col[ind[i]]] = min(Minl[col[ind[i]]], i);
        Maxr[col[ind[i]]] = max(Maxr[col[ind[i]]], i);
    }
    for(LL i = 1; i <= cnt; i++)
        if(col[e[i].f] != col[e[i].to])
            add2(col[e[i].to], col[e[i].f]), du[col[e[i].f]]++;
    top_sort();
    for(LL i = 1; i <= n; i++)
        Ans = (Ans + i * (Maxr[col[ind[i]]] - Minl[col[ind[i]]] + 1) + mod) % mod;
    printf("%lld", Ans % mod);
    return 0;
}
posted @ 2020-10-06 08:04  Nyxia  阅读(88)  评论(0编辑  收藏  举报