首页 > 其他分享 >线段树分治

线段树分治

时间:2023-04-10 22:36:49浏览次数:36  
标签:rnk 标记 int 线段 分治 操作 节点

线段树分治

线段树分治,解决的是这样一类问题:有一个时间轴,有一些操作,这些操作在时间轴上每一个的作用范围是一个区间;在某些时间或者全局询问一些关于操作效果的问题。

它的重要效果是:一是可以把所有删除操作改成撤回操作(也就是撤回当前最近一个操作,而不是删除之前随便一个操作),可以和加入做到同时间复杂度。二是和扫描线类似,具有一个时序性,可以帮助维护。

我们具体这么做:把每一个操作拆成线段树上一些完整区间,并放到对应节点上;dfs 整棵线段树,具体流程是:进入节点时,做所有该节点上的操作;然后递归儿子;然后回溯,把所有该节点上的操作撤回。

这样做有什么性质:到达某一个叶子的时候,恰好是代表一个时刻,这时候的局面就是这个时刻的局面;走到区间 \([l, r]\) 的时候,区间 \([1, r]\) 中所有操作均已经做过一遍并且有的回溯了;当前节点所有操作一定是在栈顶。

其中第一个性质方便我们回答一些时刻相关的问题;而第二个性质不要小瞧,它正是刚刚所说的扫描线性质,这意味着你在维护一些东西的时候可以打标记,然后下传标记,这个标记打上去的时间知道(可能意味着那个时间的局面上某个整体被做了什么操作),但是之后这个整体可能被做了替换(后来加上了一些东西)。那么维护打上标记的时间和加上东西的时间,下传的时候判断加上东西的时间是不是晚于打上标记的时间,如果是,那么不应该下传(这个标记打的时候这个东西还不属于那个整体)。可能还有其他妙用,但是暂时总结了这个。总而言之第二个性质是重要的。

注意点:回溯的时候,所有操作是倒序删除的。

CF1814F

【题意】
有 \(n\) 个点,\(m\) 条边,每个点有一个区间 \([l, r]\)。称 \(a, b\) 相互可达当且仅当存在路径 \(v_0 = a, v_1, ..., v_k = b\) 使得 \(v_0, ..., v_k\) 的区间无交。求哪些点和 \(1\) 相互可达。

【分析】
显然直接在原图上不能做。不妨把时间看做区间,某一个时刻 \(t\),图上有边 \((i ,j)\) 当且仅当 \((i, j) \in E\) 并且 \(t \in [l_i, r_i], t \in [l_j, r_j]\)。这样,我们对一个边的时效定义为这两个区间的交集。(考虑点是更不好的选择,更不本质,也更繁琐)

然后我们线段树分治,维护并查集(按秩合并,不用可持久化所以不用建立线段树)。

思考每一个修改是什么,是改变一个 \(fa\) 数组内的值(加边);或者不动,这依赖于之前的局面,但不依赖于之后的局面。线段树分治使得它变得可行。改变元素个数 \(O(1)\),可以直接记下来撤回。

到了叶子,是某一个时刻,和 \(1\) 相邻的所有点都在答案中,想到打标记在其根上,并且等到删边的时候下传。但是有个问题是,有可能下传的时候某些点是在根底下,但是打标记的时候并不和根属于同一集合。某个时刻打标记的意义是打在这一时刻的连通块上,所以这种点不能往下传。

因此记录一个打标记时间;然后某个边删除的时候,在哪个节点,意味着其存在的时间是这个节点表示的时间区间。判断这个区间是否包含打标记时间即可。由于 \(r\) 一定大于等于打标记时间,可以只判断 \(l\)。有新标记了,直接覆盖,因为之前和它在一起的节点们,如果断开了那么会继承标记,否则还是在同一个连通块内;后来加入的,应当让它能继承标记,因此兼容。

所以代码很简单。

时间复杂度:一共 \(O(m \log t)\) 个加边操作,每次耗时 \(O(\log n)\)。传 tag,每一个加边操作最多一次。于是总复杂度 \(O(m \log t \log n)\),可以通过。

#include<bits/stdc++.h>
#include<bits/extc++.h>
using namespace __gnu_pbds;
using namespace std;
#define int long long
//use ll instead of int.
#define f(i, a, b) for(int i = (a); i <= (b); i++)
#define cl(i, n) i.clear(),i.resize(n);
#define endl '\n'
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 1e9;
//#define cerr if(false)cerr
//#define freopen if(false)freopen
#define watch(x) cerr  << (#x) << ' '<<'i'<<'s'<<' ' << x << endl
void pofe(int number, int bitnum) {
    string s; f(i, 0, bitnum) {s += char(number & 1) + '0'; number >>= 1; } 
    reverse(s.begin(), s.end()); cerr << s << endl; 
    return;
}
template <typename TYP> void cmax(TYP &x, TYP y) {if(x < y) x = y;}
template <typename TYP> void cmin(TYP &x, TYP y) {if(x > y) x = y;}
//调不出来给我对拍!
//use std::array.
int lt[200200], rt[200200]; int fa[200200], rnk[200200]; 
int tm[200200]; int zy = 200000; int n,m;int tag[200200];
int get(int x) {if(fa[x] == x) return x; else return get(fa[x]);}
struct delinfo {int i, j, k; };
struct SGT {
    vector<pii> ap[800200];stack<delinfo> stk;
    void add(int now, int x,int y,int l,int r, pii ed) {
        if(r<l)return;
        if(x>=l&&y<=r) { ap[now].push_back(ed); return;}
        if(x > r||y < l) return;
        int mid=(x+y)>>1;add(now*2,x,mid,l,r,ed);add(now*2+1,mid+1,y,l,r,ed); 
    }
    void addedge(pii x) {
        int i = get(x.first), j = get(x.second);
        if(i == j) {stk.push({0, 0, 0}); return;}
        else {
            if(rnk[i] > rnk[j]) swap(i, j);
            fa[i] = j; stk.push({i, j, rnk[j]}); rnk[j] = max(rnk[j], rnk[i] + 1);
        }
    }
    void deledge(delinfo x, int l) {
        if(x.i == 0) return; 
        else { 
            if(l <= tag[x.j]) tag[x.i] = tag[x.j];
            fa[x.i] = x.i; rnk[x.j] = x.k;
        }   
    }
    void dfs(int now,int l,int r) {
        for(pii i:ap[now]) {addedge(i);}
        if(l!=r){int mid=(l+r)>>1;dfs(now*2,l,mid);dfs(now*2+1,mid+1,r);}
        else {tag[get(1)]=l;}
        f(T,1,(int)ap[now].size()) {deledge(stk.top(),l);stk.pop();}
    }
}sgt;
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(NULL);
    cout.tie(NULL);
    //freopen();
    //freopen();
    //time_t start = clock();
    //think twice,code once.
    //think once,debug forever.
    cin>>n>>m; f(i,1,n)fa[i]=i;
    f(i,1,n)cin>>lt[i]>>rt[i];
    f(i,1,m){
        int u,v;cin>>u>>v;sgt.add(1,1,zy,max(lt[u],lt[v]),min(rt[u],rt[v]), {u,v});
    }
    sgt.dfs(1,1,zy); vector<int> ans;
    f(i,1,n)if(tag[i])ans.push_back(i);
    sort(ans.begin(), ans.end());
    for(int i:ans)cout<<i<<" ";
    //time_t finish = clock();
    //cout << "time used:" << (finish-start) * 1.0 / CLOCKS_PER_SEC <<"s"<< endl;
    return 0;
}
/*
2023/4/10
start thinking at 10:00


start coding at 21:30
finish debugging at 21:45
*/

标签:rnk,标记,int,线段,分治,操作,节点
From: https://www.cnblogs.com/Zeardoe/p/17304558.html

相关文章

  • hdu-4533(线段树+区间合并)
    约会安排HDU-4553跟hdu-1540(线段树+区间合并)-魏老6-博客园(cnblogs.com)是一样,但是要写两个线段树。线段树维护,最长前缀pre,最长后缀suf,以及最大连续连续区间sum。1代表空,0代表时间被占了还有几个注意事项:当是DS时,只能查询和修改屌丝树;当是NS时,先判断屌丝树能不......
  • 可持久化线段树(主席树)
              代码#include<bits/stdc++.h>usingnamespacestd;constintN=4e7+10;intn,m,t,top,rt,mode,x,y;intf[N],a[N],root[N];structkkk{intl,r,val;}tree[N];intclone(intnode){top++;tree[top]=tree[node];return......
  • poj-3367(线段树+区间合并)
    HotelPOJ-3667思路:与hdu-1540(线段树+区间合并)-魏老6-博客园(cnblogs.com)类似,只不过是区间修改,多维护一个最大连续区间sum。#define_CRT_SECURE_NO_WARNINGS1#include<algorithm>#include<fstream>#include<iostream>#include<cstdio>#include<deque>#includ......
  • hdu-1540(线段树+区间合并)
    TunnelWarfareHDU-1540思路:没被摧毁的村庄为1,否则为0,用len记录线段树维护区间的两个信息:前缀最长1的序列pre后缀最长1的序列suf父节点与左右子节点的关系://lc为左节点,rc为右节点1.若左右结点都不满1,则tr[p].pre=tr[lc].pre,tr[p].suf=tr[rc].suf2.若左节点满1,tr......
  • [蓝桥杯 2021 国 AB] 翻转括号序列(线段树上二分)
    [蓝桥杯2021国AB]翻转括号序列题目描述给定一个长度为\(n\)的括号序列,要求支持两种操作:将\(\left[L_{i},R_{i}\right]\)区间内(序列中的第\(L_{i}\)个字符到第\(R_{i}\)个字符)的括号全部翻转(左括号变成右括号,右括号变成左括号)。求出以\(L_{i}\)为左端点......
  • UVA - 10245 The Closest Pair Problem 分治法
    题目大意:给出一系列的点,求出距离最近的两点的距离的大小,如果该距离大于10000,则输出INFINITY,如果不是的话,输出保留四个小数点的距离解题思路:如果裸的枚举的话,无疑是TLE,O(n^2)的算法既然不行的话,就换分治法试试,将其按X轴坐标的大小排序,由小到大,分别求出每部分的大小,然后进行比较,比较难......
  • CAD如何测量连续线段长度?CAD测量连续线段长度步骤
    在CAD绘图过程中,经常会绘制一些连续的线段,如果想要知道这些连续线段长度的话,该怎么操作吗?CAD如何测量连续线段长度?下面小编就以浩辰CAD软件为例来给大家分享一下CAD测量连续线段长度的具体操作步骤吧!CAD测量连续线段长度步骤:浩辰CAD软件中已经考虑到了这种需求,在CAD测量命令(DIST......
  • 动态开点线段树&线段树合并学习笔记
    动态开点线段树使用场景\(4\timesn\)开不下。值域需要平移(有负数)。什么时候开点显然,访问的节点不存在时(只会在修改递归时开点)。trick区间里面有负数时,\(mid=(l+R-1)/2\)。防止越界。例如区间\([-1,0]\)。开点上限考虑到update一次最多开\(\logV\)个......
  • 第二十届浙大城市学院程序设计竞赛 I.Magic Tree DFS序线段树
    传送门大致思路:  我们知道dfs序上的整颗子树dfs序编号连续,因为每次删除一个点或者新增一个点都导致子树上所有点的深度加一或者减一。由于是区间修改所以我们考虑dfs序上建线段树。  #include<iostream>#include<cstring>#include<iomanip>#include<algorithm>#in......
  • 区间和线段树封装模板
    区间和线段树封装模板,开箱即用注意:线段树大小最多支持\(2^{30}-1\)个数声明方法:SegSumTree<typename>st,typename为线段树存储的类型(建议只填写整数类型),建立一颗空线段树,后续必须先用rebuild或resize初始化SegSumTree<typename>st(n)建立一颗定义了长度的空线段树,n为线段树维......