首页 > 其他分享 >线段树优化建图 (CF786B、SNOI2017炸弹)

线段树优化建图 (CF786B、SNOI2017炸弹)

时间:2022-10-22 10:22:31浏览次数:80  
标签:return se2 int 线段 建图 SNOI2017 CF786B void dis

先来看板子题: CF786B

可以发现,如果对着区间内的每一个点都建一条边,然后跑最短路,我们无论是在空间还是时间复杂度上都是过不去的。因此,我们请出老朋友线段树。

这是图片

参考上图。修建两棵线段树。其中一棵从父亲向左右儿子连边,若为有权图则边权为 \(0\), 以此保证每一个区间可以到达区间内部的每一个点。
第二课由儿子向父亲连边,以此保证每个点可以到达包含他的区间。
然后,单点和其对应的单点连边,以此保证互相可以获取对方信息。

之后考虑操作。
令下方线段树用于发出连边,可以参考图中黄色线段。这样便能保证可以到达对应区间内的所有点了。
(注意,在初始时,\(i\) 与 \(i + K\)只能互相到达,是不能到别的区间内部的,观察图即可发现该要求得到保证。)

值得注意的是,此处的单点应使用线段树中的节点编号,以避免混淆。

那么上板子题代码:

#include <bits/stdc++.h>
using namespace std;
#define N 3000010
#define ll long long
const int K = 500010; 

template <class T>
inline void read(T& a){
	T x = 0, s = 1;
	char c = getchar();
	while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + (c ^ '0'); c = getchar(); }
	a = x * s;
	return ;
}

int n, Q, s; 
int a[N];  // 每个点在线段树中的编号

struct node{
  int u, v, w, next; 
} t[N];
int head[N]; 

int bian = 0;
inline void addedge(int u, int v, int w){
  t[++bian] = (node){u, v, w, head[u]}, head[u] = bian;
  return ; 
}

struct Segment_tree{
  #define lson (o<<1)
  #define rson (o<<1|1)

  void build(int o, int l, int r){
    if(l == r){
      a[l] = o; 
      return ; 
    }
    
    addedge(o, lson, 0); addedge(o, rson, 0); 
    addedge(lson + K, o + K, 0); addedge(rson + K, o + K, 0);
    int mid = l + r >>1;
    build(lson, l, mid);
    build(rson, mid + 1, r); 
    return ; 
  }

  void update(int o, int l, int r, int in, int end, int k, int w, int opt){
    if(l > end || r < in) return ; 
    if(l >= in && r <= end){
      if(opt == 2){  // v -> [l,r]
        addedge(a[k] + K, o, w); 
      }
      else addedge(o + K, a[k], w); 
      return ;
    }
    int mid = l + r >> 1;
    update(lson, l, mid, in, end, k, w, opt);
    update(rson, mid + 1, r, in, end, k, w, opt); 
    return ; 
  }


} tree; 

struct point{
  ll dis, id; 
  bool operator < (const point &a) const{
    return dis > a.dis; 
  }
} ; 

priority_queue <point> q; 
ll dis[N]; 
bool vis[N];  

void dij(int s){
  memset(dis, 0x3f3f3f3f3f3f3f, sizeof(dis)); 
  dis[s] = 0; 
  q.push((point){0, s}); 
  while(!q.empty()){
    int u = q.top().id;  q.pop(); 
    if(!vis[u]){
      vis[u] = 1; 
      for(int i = head[u]; i; i = t[i].next){
        
        int v = t[i].v;
        if(dis[v] > dis[u] + t[i].w){
          dis[v] = dis[u] + t[i].w; 
          if(!vis[v])q.push((point){dis[v], v}); 
        }
      }
    }
  }

  return ; 
}

signed main(){
  // freopen("hh.txt", "r", stdin); 
  read(n), read(Q), read(s); 
  tree.build(1, 1, n); 
  for(int i = 1; i <= n; i++){
    addedge(a[i], a[i] + K, 0);
    addedge(a[i] + K, a[i], 0); 
  }
  while(Q--){
    ll opt, x, l, r, w;
    read(opt); 
    if(opt == 1){
      read(x), read(l), read(w);
      addedge(a[x] + K, a[l], w); 
    }
    else{
      read(x), read(l), read(r), read(w); 
      tree.update(1, 1, n, l, r, x, w, opt); 
    }
  }

  dij(a[s] + K); 
  for(int i = 1; i <= n; i++){
    printf("%lld ", dis[a[i]] <= 0x3f3f3f3f3f3f3f ? dis[a[i]] : -1); 
  }
  return 0;
}

对于另外一道例题: [P5025 SNOI2017炸弹] (https://www.luogu.com.cn/problem/P5025)

首先使用 \(lower_bound\) 求出每一个炸弹可以对应的单层引爆区间,然后用单向边指向该区间。按理来说直接跑图即可,但考虑到炸弹之间可能可以互相引爆,因此先缩点去环。
还有一点,每个炸弹引爆的点是一条条线段,其交集可能被重复计算,因此要统计每一个节点所包含的区间(左右端点 \(l,r\))来计算。(说明:只有有交集线段可以合并,未交集线段不会被合并到一起,因此该方法正确。)

注意,该题其实不需要第二课线段树,因为只有单点向区间加边这一个方向。题解中的两棵线段树纯属为了练习(折磨自己)

#include <bits/stdc++.h>
using namespace std;
#define N 8000010
#define ll long long
const ll K = 2e6 + 1; 
const ll mod = 1e9 + 7; 

template <class T>
inline void read(T& a){
	T x = 0, s = 1;
	char c = getchar();
	while(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + (c ^ '0'); c = getchar(); }
	a = x * s;
	return ;
}

struct node{
  int u, v, next;
} t[N], t1[N];
int head[N];

int bian = 0;
inline void addedge(int u, int v){
  t[++bian] = (node){u, v, head[u]}, head[u] = bian;
  return ; 
}

int bian1 = 0;
int head1[N]; 
inline void addedge1(int u, int v){
  t1[++bian1] = (node){u, v, head1[u]}, head1[u] = bian1; 
  return ; 
}

ll n, x[N]; 
ll r[N]; 

int a[N]; 

struct seg{
  int l, r;

  seg(){
    this->l = 1e18; 
    this->r = 0; 
    return ; 
  }
} se[N];   // 每个节点对应的区间 
seg se2[N];     // 缩点后对应的区间
ll tot = 0;    // 节点总数

struct Segment_tree{
  #define lson (o<<1)
  #define rson (o<<1|1)

  void build(int o, int l, int r){
    se[o].l = l, se[o].r = r; 
    tot = max(tot, (ll)(o + K));
    if(l == r){
      // tot = max(tot, (ll)(o));
      a[l] = o;
      return ; 
    }
    int mid = l + r >> 1;
    build(lson, l, mid); build(rson, mid + 1, r);   
    addedge(o, lson); addedge(o, rson);         
    addedge(lson + K, o + K); addedge(rson + K, o + K);   
    return ; 
  }

  void update(int o, int l, int r, int in, int end, int k){
    if(l > end || r < in) return ;
    if(l >= in && r <= end){  // 点向区间加边
      addedge(a[k] + K, o);
      return ; 
    }
    int mid = l + r >> 1;
    update(lson, l, mid, in, end, k);
    update(rson, mid + 1, r, in, end, k); 
    return ; 
  }

} tree; 

struct point{
  ll x, r; 
  int id; 

  bool operator < (const point &a) const{
    return x < a.x; 
  }

} p[N]; 

int dfn[N], low[N]; 
int id = 0;
int stac[N], top = 0; 
int scc[N], cnt = 0; 
bool vis[N]; 

void tarjan(int u){
  low[u] = dfn[u] = ++id; 
  vis[u] = 1;
  stac[++top] = u; 
  for(int i = head[u]; i; i = t[i].next){
    int v = t[i].v;
    if(!dfn[v]){
      tarjan(v); 
      low[u] = min(low[u], low[v]); 
    } else if(vis[v]) low[u] = min(low[u], dfn[v]); 
  }
  if(low[u] == dfn[u]){
    int cur;
    cnt++; 
    do{
      cur = stac[top--]; 
      scc[cur] = cnt;
      se2[cnt].l = min(se[cur].l, se2[cnt].l); 
      se2[cnt].r = max(se[cur].r, se2[cnt].r); 
      vis[cur] = 0; 
    } while(cur != u); 
  }
  return ; 
}


void dfs(int u){
  vis[u] = 1;
  for(int i = head1[u]; i; i = t1[i].next){
    int v = t1[i].v; 
    if(!vis[v]) dfs(v); 
    se2[u].l = min(se2[u].l, se2[v].l); 
    se2[u].r = max(se2[u].r, se2[v].r); 
  }
  return ; 
}

signed main(){
  // freopen("hh.txt", "r", stdin); 
  read(n);
  tree.build(1, 1, n);
  for(int i = 1; i <= n; i++){
    addedge(a[i], a[i] + K); 
    addedge(a[i] + K, a[i]); 
  }
  for(int i = 1; i <= n; i++){
    read(p[i].x); read(p[i].r); 
    p[i].id = i; 
  }
  sort(p + 1, p + n + 1); 
  for(int i = 1; i <= n; i++){
    ll L, R; 
    ll lnum = p[i].x - p[i].r; 
    ll rnum = p[i].x + p[i].r; 
    L = lower_bound(p + 1, p + n + 1, (point){lnum, 0, 0}) - p; 
    R = lower_bound(p + 1, p + n + 1, (point){rnum + 1, 0, 0}) - p - 1; 
    tree.update(1, 1, n, L, R, i); 
  }

  for(int i = 1; i <= tot; i++)
    if(!dfn[i]) tarjan(i); 

  for(int u = 1; u <= tot; u++){
    for(int i = head[u]; i; i = t[i].next){
      int v = t[i].v; 
      if(scc[u] != scc[v]){
        addedge1(scc[u], scc[v]); 
      }
    }
  }

  // memset(vis, 0, sizeof(vis));
  for(int i = 1; i <= cnt; i++){
    dfs(i); 
  }
  ll ans = 0; 
  for(int i = 1; i <= n; i++)
    ans = (ans + (ll)i * (se2[scc[a[i] + K]].r - se2[scc[a[i] + K]].l + 1) % mod) % mod;
  cout << ans << endl; 

  return 0;
}


标签:return,se2,int,线段,建图,SNOI2017,CF786B,void,dis
From: https://www.cnblogs.com/wondering-world/p/16815441.html

相关文章