线段树分治
前言
这个东西常和线段树二分搞混,然而这俩是完全不一样的东西。
抄了 luogu 中 xht 的题解。
核心思想
考虑这样一个问题:
- 有一些操作,每个操作只在 l∼r 的时间段内有效。
- 有一些询问,每个询问某一个时间点所有操作的贡献。
对于这样的询问,我们可以离线后在时间轴上建一棵线段树,这样对于每个操作,相当于在线段树上进行区间操作。
遍历整颗线段树,到达每个节点时执行相应的操作,然后继续向下递归,到达叶子节点时统计贡献,回溯时撤销操作即可。
这样的思想被称为线段树分治,可以在低时间复杂度内解决一类在线算法并不优秀的问题
题目引入
P5787 二分图 /【模板】线段树分治
首先把操作堆到一个时间的线段树上,然后考虑从根节点走到叶子结点相当于是维护加边,最后到叶子节点询问是否存在奇环(二分图充要条件),接着从叶子节点走回根节点,维护撤销加边。
可以发现这个加边、判断奇环、撤销边的过程可以用可撤销扩展域并查集维护。
时间复杂度
另外这个题面写的比较糖,时间段
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int n,m,k;
int fa[N*2],sz[N*2];
inline int cg(int x){
if(x<=n)return x+n;
return x-n;
}
int find(int x){if(x==fa[x])return x;return find(fa[x]);}
stack<int>st;
inline void merge(int x,int y){
x=find(x),y=find(y);
if(x==y){return;}
if(sz[x]>sz[y])swap(x,y);
fa[x]=y;
sz[y]+=sz[x];st.push(x);
}
inline void split(){
int x=st.top();st.pop();
sz[fa[x]]-=sz[x];fa[x]=x;
}
struct Izayoi_Sakuya{
vector<pair<int,int> >g[N<<2];
inline int ls(int p){return p<<1;}inline int rs(int p){return p<<1|1;}
void change(int p,int nl,int nr,int ml,int mr,pair<int,int>k){
if(nl<=ml&&mr<=nr){g[p].push_back(k);return;}
int mid=ml+mr>>1;
if(nl<=mid){change(ls(p),nl,nr,ml,mid,k);}
if(mid+1<=nr){change(rs(p),nl,nr,mid+1,mr,k);}
}
void query(int p,int ml,int mr){
int tim=st.size();bool flag=1;
for(int i=0;i<g[p].size();i++){
pair<int,int>tmp=g[p][i];
if(find(tmp.first)==find(tmp.second)){
for(int j=ml;j<=mr;j++){cout<<"No\n";}
flag=0;break;
}
merge(tmp.first,cg(tmp.second));
merge(tmp.second,cg(tmp.first));
}
if(flag){
int mid=ml+mr>>1;
if(ml==mr){cout<<"Yes\n";}
else {query(ls(p),ml,mid);query(rs(p),mid+1,mr);}
}
while(st.size()>tim){split();}
}
}IS;
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m>>k;
for(int i=0;i<N*2;i++)fa[i]=i,sz[i]=1;
for(int i=1;i<=m;i++){
int x,y,l,r;
cin>>x>>y>>l>>r;
if(l==r)continue;
IS.change(1,l+1,r,1,k,{x,y});
}
IS.query(1,1,k);
return 0;
}
不过值得注意的是,这东西一般不能支持一遍加入删除(即修改)一遍查询,因为这样的时间复杂度是错的。
比如说我每次都是全局添加线段,每次都全局查询,那么这个
所以通常这种题都是修改删除完了之后最后问你一个很大的问题。(通常是遍历整个线段树)
更多例题
CF1681F Unique Occurrences
好啊,懵逼了,这甚至都感觉和线段树分治没关系!
所以要把边理解为添加边和删除边。
我们发现如果把某一种颜色
这东西就直接写了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5;
int n;vector<pair<int,int> >e[N];
ll fa[N],sz[N];ll genshin;
int find(int x){if(fa[x]==x)return x;return find(fa[x]);}
stack<int>st;
inline void merge(int x,int y){
x=find(x),y=find(y);if(x==y){return;}
if(sz[x]>sz[y])swap(x,y);
fa[x]=y;
sz[y]+=sz[x];
st.push(x);
}
inline void split(){int x=st.top();sz[fa[x]]-=sz[x];fa[x]=x;st.pop();}
struct Izayoi_Sakuya{
vector<pair<int,int> >g[N<<2];
inline int ls(int p){return p<<1;}inline int rs(int p){return p<<1|1;}
void change(int p,int nl,int nr,int ml,int mr,pair<int,int>k){
if(nl>nr)return;
if(nl<=ml&&mr<=nr){g[p].push_back(k);return;}
int mid=ml+mr>>1;
if(nl<=mid){change(ls(p),nl,nr,ml,mid,k);}
if(mid+1<=nr){change(rs(p),nl,nr,mid+1,mr,k);}
}
void query(int p,int ml,int mr){
int tim=st.size();
for(int i=0;i<g[p].size();i++){merge(g[p][i].first,g[p][i].second);}
if(ml==mr){
for(int i=0;i<e[ml].size();i++){
genshin+=sz[find(e[ml][i].first)]*sz[find(e[ml][i].second)];
}while(st.size()>tim)split();
return;
}
int mid=ml+mr>>1;
query(ls(p),ml,mid);query(rs(p),mid+1,mr);
while(st.size()>tim)split();
}
}IS;
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n;for(int i=0;i<N;i++)fa[i]=i,sz[i]=1;
for(int i=1;i<n;i++){
int u,v,w;cin>>u>>v>>w;e[w].push_back(make_pair(u,v));
IS.change(1,1,w-1,1,n,make_pair(u,v));
IS.change(1,w+1,n,1,n,make_pair(u,v));
}
IS.query(1,1,n);cout<<genshin;
return 0;
}
P5631 最小mex生成树
没有什么区别(
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5,MAXW=1e5+5;
int n,m;
int fa[N],sz[N];
int find(int x){if(fa[x]==x)return x;return find(fa[x]);}
stack<int>st;
inline void merge(int x,int y){
x=find(x),y=find(y);if(x==y){return;}
if(sz[x]>sz[y])swap(x,y);
fa[x]=y;
sz[y]+=sz[x];
st.push(x);
}
inline void split(){int x=st.top();sz[fa[x]]-=sz[x];fa[x]=x;st.pop();}
struct Izayoi_Sakuya{
vector<pair<int,int> >g[N<<2];
inline int ls(int p){return p<<1;}inline int rs(int p){return p<<1|1;}
void change(int p,int nl,int nr,int ml,int mr,pair<int,int>k){
if(nl>nr)return;
if(nl<=ml&&mr<=nr){g[p].push_back(k);return;}
int mid=ml+mr>>1;
if(nl<=mid){change(ls(p),nl,nr,ml,mid,k);}
if(mid+1<=nr){change(rs(p),nl,nr,mid+1,mr,k);}
}
void query(int p,int ml,int mr){
int tim=st.size();
for(int i=0;i<g[p].size();i++){merge(g[p][i].first,g[p][i].second);}
if(ml==mr){
if(sz[find(1)]==n){cout<<ml<<endl;exit(0);}
while(st.size()>tim)split();
return;
}
int mid=ml+mr>>1;
query(ls(p),ml,mid);query(rs(p),mid+1,mr);
while(st.size()>tim)split();
}
}IS;
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>n>>m;for(int i=0;i<N;i++)fa[i]=i,sz[i]=1;
for(int i=1;i<=m;i++){
int u,v,w;cin>>u>>v>>w;
IS.change(1,1,w-1,1,MAXW,make_pair(u,v));
IS.change(1,w+1,MAXW,1,MAXW,make_pair(u,v));
}
IS.query(1,1,MAXW);
return 0;
}