P5025 [SNOI2017]炸弹 [线段树优化建图 + Tarjan]
题目描述见上方链接 .
- 用线段树优化建图建图, 得到一个边数为 的图 .
不会的可以看这里 线段树优化建图
- 然后使用 将图 缩点 变成 , 时间复杂度
-
在联通块之间连边, 注意与前面的边区分开 .
-
对每个连通块 , 统计答案 .
这里给出样例建出的图
带红色的节点为 出树 节点 .
- 线段树优化建图时记得树内连边 .
- 不是图中的节点不能计入 联通块的大小, 仅标记在 入树 中的叶子节点即可 .
- 建完图后, 基本上用的只是 线段树 中的节点了, 所以要注意不要混淆 线段树 与 原图 的节点编号 .
- 判重边, 否则 时会答案被加多 .
代码, 这里使用了两个线段树, 因此后面的点 , 建一颗线段树可以 .
#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;
}