P5025 [SNOI2017]炸弹 [线段树优化建图 + Tarjan]

炸 弹 炸弹

题目描述见上方链接 .


正 解 部 分 \color{red}{正解部分}

  • 用线段树优化建图建图, 得到一个边数为 N l o g N NlogN NlogN 的图 .

不会的可以看这里 线段树优化建图

  • 然后使用 T a r j a n Tarjan Tarjan 将图 缩点 变成 D A G DAG DAG, 时间复杂度 O ( N l o g N ) O(NlogN) O(NlogN)

不会的可以看这里 T a r j a n Tarjan Tarjan

  • 在联通块之间连边, 注意与前面的边区分开 .

  • 对每个连通块 D F S DFS DFS, 统计答案 .


这里给出样例建出的图 ↓ ↓

在这里插入图片描述带红色的节点为 出树 节点 .


实 现 部 分 \color{red}{实现部分}

  • 线段树优化建图时记得树内连边 .
  • 不是图中的节点不能计入 联通块的大小, 仅标记在 入树 中的叶子节点即可 .
  • 建完图后, 基本上用的只是 线段树 中的节点了, 所以要注意不要混淆 线段树原图 的节点编号 .
  • 判重边, 否则 D F S DFS DFS 时会答案被加多 .

82 p t s 82pts 82pts 代码, 这里使用了两个线段树, 因此后面的点 T L E TLE TLE, 建一颗线段树可以 A C AC AC .

#include<set>
#include<stack>
#include<vector>
#include<cstdio>
#include<cctype>
#include<algorithm>
#define reg register
#define pb push_back
typedef long long ll;

const int maxn = 6e6 + 105;
const int mod = 1e9 + 7;

int N;
int num_1;
int num_2;
int dfn_tim;
int node_cnt;
int block_num;
int rot[2];
ll X[maxn];
ll Y[maxn];
ll R[maxn];
int head[maxn];
int head_2[maxn];
int dfn[maxn];
int low[maxn];
int Ans[maxn];
int block_id[maxn];
int block_size[maxn];
int Mp[2][maxn];

bool is_g[maxn];
bool In_stk[maxn];

std::vector <int> block[maxn];
std::stack <int> stk;
std::set <int> sete[maxn];

struct Edge{ int nxt, to; } edge[maxn], edge_2[maxn];

struct Node{ int l, r, lt, rt; } T[maxn<<2];

void Add(int from, int to){
        edge[++ num_1] = (Edge){ head[from], to };
        head[from] = num_1;
}

void Add_2(int from, int to){
        edge_2[++ num_2] = (Edge){ head_2[from], to };
        head_2[from] = num_2;
}

void Build(int &k, int l, int r, int opt){ //
        k = ++ node_cnt;
        T[k].l = l, T[k].r = r;
        if(l == r){ is_g[k] = !opt, Mp[opt][l] = k; return ; }
        int mid = l+r >> 1;
        Build(T[k].lt, l, mid, opt), Build(T[k].rt, mid+1, r, opt);
        if(!opt) Add(T[k].lt, k), Add(T[k].rt, k);
        else Add(k, T[k].lt), Add(k, T[k].rt);
}

void Connect_0(int k){ //
        int l = T[k].l, r = T[k].r;
        if(l == r){ Add(Mp[1][l], Mp[0][l]); return ; }
        Connect_0(T[k].lt), Connect_0(T[k].rt);
}

void Connect_1(int k, int L, int R, int o_id){ // 1
        int l = T[k].l, r = T[k].r;
        if(L <= l && r <= R){
                if(l == r && o_id == l) return ;
                Add(Mp[0][o_id], k); return ;
        }
        int mid = l+r >> 1;
        if(L <= mid) Connect_1(T[k].lt, L, R, o_id);
        if(R > mid)  Connect_1(T[k].rt, L, R, o_id);
}

void Tarjan(int k){
        In_stk[k] = 1; stk.push(k);
        low[k] = dfn[k] = ++ dfn_tim;
        for(reg int i = head[k]; i; i = edge[i].nxt){
                int to = edge[i].to;
                if(!dfn[to]) Tarjan(to), low[k] = std::min(low[k], low[to]);
                else if(In_stk[to]) low[k] = std::min(low[k], dfn[to]); //#
        }
        if(dfn[k] == low[k]){
                block_size[++ block_num] = is_g[k];
                block_id[k] = block_num; 
                block[block_num].pb(k);
                int &t = block_size[block_num];
                while(stk.top() != k){
                        t += is_g[stk.top()]; 
                        block[block_num].pb(stk.top());
                        block_id[stk.top()] = block_num;
                        In_stk[stk.top()] = 0; stk.pop();
                }
                In_stk[stk.top()] = 0;stk.pop();
        }
}

void Out_line(int k, int b_id){
        for(reg int i = head[k]; i; i = edge[i].nxt){
                int to = edge[i].to;
                if(b_id == block_id[to]) continue ;
                if(!sete[b_id].count(block_id[to])){
                        Add_2(b_id, block_id[to]);
                        sete[b_id].insert(block_id[to]);
                }
        }
}

void DFS(int k){
        if(Ans[k]) return ; 
        Ans[k] = block_size[k];
        for(reg int i = head_2[k]; i; i = edge_2[i].nxt){
                int to = edge_2[i].to;
                DFS(to); Ans[k] += Ans[to];
        }
}

int main(){
        scanf("%d", &N);
        for(reg int i = 1; i <= N; i ++) X[i] = read(), R[i] = read();
        Build(rot[0], 1, N, 0), Build(rot[1], 1, N, 1);
        Connect_0(rot[0]);
        for(reg int i = 1; i <= N; i ++){
                int l_lim = std::lower_bound(X+1, X+N+1, X[i]-R[i]) - X;
                int r_lim = std::upper_bound(X+1, X+N+1, X[i]+R[i]) - X-1;
                Connect_1(rot[1], l_lim, r_lim, i);
        }
        for(reg int i = 1; i <= N; i ++){
                int id = Mp[0][i];
                if(!dfn[id]) Tarjan(id);
        }
        for(reg int i = 1; i <= block_num; i ++){
                int size = block[i].size();
                for(reg int j = 0; j < size; j ++){ 
                        int id = block[i][j];
                        Out_line(id, i);
                }
        }
        int ANS = 0;
        for(reg int i = 1; i <= block_num; i ++) DFS(i);
        for(reg int i = 1; i <= N; i ++) ANS = (1ll*ANS + (1ll*i*Ans[block_id[Mp[0][i]]]%mod)) % mod;
        printf("%d\n", ANS);
        return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值