前置知识 : 线段树(只需要了解其结构) 支持撤销的数据结构,如可撤销并查集,可持久化数据结构等。
线段树分治是什么
一种离线算法,用于处理假如某些信息会在某个时间段出现,求某个时刻的信息并。
常见的离线算法还有CDQ分治,考虑一下这两个算法的区别。
CDQ分治:对于每个操作,考虑其对后面询问的影响。
线段树分治: 将修改看做是一段时间区间的修改,将询问看为时间点。
啥时候用
多个操作,每一个操作的影响范围是一个区间(时间区间,询问区间)。
多个询问,询问(某个时间时,某个操作后的)答案。
如何实现
先考虑如何判断二分图。
当一个图为二分图时,当且仅当所有边的两个点不在同一集合中。然后就可以用拓展域并查集来判断了。
假设一条边为\(x\leftrightarrow y\),那么等价于\(x\)与\(y+n\)在同一集合,\(x+n\)与\(y\)在同一集合。
线段树区间\([l,r]\)维护的是在\([l,r]\)出现的边,用并查集维护这个东西。
然后发现当从\([l,r]\)中退出时,要将这个区间的贡献删去,用普通并查集就维护不了了,用可撤销并查集即可。
非常好理解是不是
废话少说,上代码。
点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t; i += p)
#define drep(i,s,t,p) for(int i = s;i >= t; i -= p)
#define Infile(x) freopen(#x".in","r",stdin)
#define Outfile(x) freopen(#x".out","w",stdout)
#define Ansfile(x) freopen(#x".ans","w",stdout)
#define Errfile(x) freopen(#x".err","w",stderr)
#ifdef LOCAL
FILE *InFile = Infile(in),*OutFile = Outfile(out);
// FILE *ErrFile = Errfile(err);
#else
FILE *InFile = stdin,*OutFile = stdout;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
#define eb emplace_back
const int N = 1e5 + 10;
int n,m,k;
struct EUDSU{
vector<int> fa,siz,sta;
inline void init(int n){
int len = (n+1)*2+1;
fa.resize(len),siz.resize(len);
rep(i,1,len,1) fa[i] = i,siz[i] = 1;
}
inline int get_fa(int x){while(x != fa[x]) x = fa[x];return x;}
inline void Merge(int x,int y){
int fx = get_fa(x),fy = get_fa(y);
if(fx == fy) return;
if(siz[fx] < siz[fy]) swap(fx,fy),swap(x,y);
fa[fy] = fx;
siz[fx] += siz[fy];
sta.eb(fy);
}
inline void undo(){
int y = sta.back();sta.pop_back();
siz[fa[y]] -= siz[y];fa[y] = y;
}
inline void undo(int k){while(sta.size() > k) undo();}
}D;
struct node{int x,y;}e[N<<1];
struct Segment_Tree_Divide{
vector<int> t[N<<3];
void upd(int k,int l,int r,int ql,int qr,int x){
if(ql <= l && r <= qr) return t[k].eb(x),void();
int mid = (l + r)>>1;
if(ql <= mid) upd(k<<1,l,mid,ql,qr,x);
if(qr > mid) upd(k<<1|1,mid+1,r,ql,qr,x);
}
void qry(int k,int l,int r){
bool flag = true;
int lasttop = D.sta.size();
for(auto i:t[k]){
int x = e[i].x,y = e[i].y;
int fx = D.get_fa(x),fy = D.get_fa(y);
if(fx == fy){
flag = false;
rep(i,l,r,1) cout<<"No\n";
break;
}
D.Merge(e[i].x,e[i].y+n);
D.Merge(e[i].x+n,e[i].y);
}
if(flag){
if(l == r) cout<<"Yes\n";
else{
int mid = (l + r) >> 1;
qry(k<<1,l,mid);qry(k<<1|1,mid+1,r);
}
}
D.undo(lasttop);
}
}T;
inline void solve(){
cin>>n>>m>>k;
D.init(n);
rep(i,1,m,1){
cin>>e[i].x>>e[i].y;
int l,r;cin>>l>>r;l++;
T.upd(1,1,k,l,r,i);
}
T.qry(1,1,k);
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
solve();
}
线段树分治大体都是这个思路。
例题
板子题,记录一下每个数的贡献区间即可。
点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t; i += p)
#define drep(i,s,t,p) for(int i = s;i >= t; i -= p)
#define Infile(x) freopen(#x".in","r",stdin)
#define Outfile(x) freopen(#x".out","w",stdout)
#define Ansfile(x) freopen(#x".ans","w",stdout)
#define Errfile(x) freopen(#x".err","w",stderr)
#ifdef LOCAL
FILE *InFile = Infile(in),*OutFile = Outfile(out);
// FILE *ErrFile = Errfile(err);
#else
FILE *InFile = stdin,*OutFile = stdout;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 1e5 + 10;
int bg[N],ed[N],a[N],n,mod;
struct Segment_Tree_Divide{
struct segment_tree{
int l,r,z,v;
#define l(x) tree[x].l
#define r(x) tree[x].r
#define v(x) tree[x].v
#define z(x) tree[x].z
}tree[N<<2];
inline void P(int k){v(k) = 1ll*v(k<<1)*v(k<<1|1)%mod;}
inline void D(int k){
if(z(k) != 1){
int ls = k<<1,rs = k<<1|1;
z(ls) = 1ll*z(ls)*z(k)%mod;
z(rs) = 1ll*z(rs)*z(k)%mod;
v(ls) = 1ll*v(ls)*z(k)%mod;
v(rs) = 1ll*v(rs)*z(k)%mod;
z(k) = 1;
}
}
void B(int k,int l,int r){
l(k) = l,r(k) = r,v(k) = z(k) = 1;
if(l == r) return;
int mid = (l + r) >> 1;
B(k<<1,l,mid);B(k<<1|1,mid+1,r);
}
void U(int k,int l,int r,int v){
if(l <= l(k) && r(k) <= r) return v(k) = 1ll*v(k)*v%mod,z(k) = 1ll*z(k)*v%mod,void();
D(k);
int mid = (l(k) + r(k)) >> 1;
if(l <= mid) U(k<<1,l,r,v);
if(r > mid) U(k<<1|1,l,r,v);
P(k);
}
void Q(int k){
if(l(k) == r(k)) return cout<<v(k)<<'\n',void();
D(k);Q(k<<1);Q(k<<1|1);
}
}T;
inline void solve(){
cin>>n>>mod;
T.B(1,1,n);
rep(i,1,n,1){
a[i] = 0;
int op,x;
cin>>op>>x;
if(op ^ 1) ed[x] = i-1;
else a[i] = x,bg[i] = i,ed[i] = n;
}
rep(i,1,n,1)
if(a[i]) T.U(1,bg[i],ed[i],a[i]);
T.Q(1);
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
int T;cin>>T;while(T--) solve();
}
比较有意义的一道题,一个常见的套路。
每次询问的时候等价于将这条边断掉,然后询问\(siz_x\times siz_y\)。
点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t; i += p)
#define drep(i,s,t,p) for(int i = s;i >= t; i -= p)
#define Infile(x) freopen(#x".in","r",stdin)
#define Outfile(x) freopen(#x".out","w",stdout)
#define Ansfile(x) freopen(#x".ans","w",stdout)
#define Errfile(x) freopen(#x".err","w",stderr)
#ifdef LOCAL
FILE *InFile = Infile(in),*OutFile = Outfile(out);
// FILE *ErrFile = Errfile(err);
#else
FILE *InFile = stdin,*OutFile = stdout;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 1e5 + 10;
#define vec vector
#define rz resize
#define eb emplace_back
struct DSU{
vec<int> fa,siz,rk;
struct undo_node{int x,y,fx,ry,sy;};vec<undo_node> sta;
inline int get_fa(int x){while(x != fa[x]) x = fa[x];return x;}
inline void merge(int x,int y){
x = get_fa(x),y = get_fa(y);
if(x == y) return;
if(rk[x] > rk[y]) swap(x,y);
sta.push_back({x,y,fa[x],rk[y],siz[y]});
fa[x] = y,siz[y] += siz[x];
if(rk[x] == rk[y]) ++rk[y];
}
inline int Q(int x){return siz[get_fa(x)];}
inline void undo(){
auto res = sta.back();sta.pop_back();
int x = res.x,y = res.y;
fa[x] = res.fx,rk[y] = res.ry,siz[y] = res.sy;
}
inline void undo(int res){while(sta.size() > res) undo();}
inline void init(int n){
fa.rz(n+1),siz.rz(n+1),rk.rz(n+1);
rep(i,1,n,1) fa[i] = i,siz[i] = rk[i] = 1;
}
}D;
#define pii pair<int,int>
#define mk make_pair
int n,q;
struct Que{char op;int x,y;}Q[N];
struct Segment_Tree_Divide{
vec<pii> t[N<<2];
void I(int k,int l,int r,int ql,int qr,int x,int y){
if(ql <= l && r <= qr) return t[k].eb(mk(x,y));
int mid = (l + r) >> 1;
if(ql <= mid) I(k<<1,l,mid,ql,qr,x,y);
if(qr > mid) I(k<<1|1,mid+1,r,ql,qr,x,y);
}
void qry(int k,int l,int r){
int top = D.sta.size();
for(pii i:t[k]) D.merge(i.first,i.second);
if(l == r){
if(Q[l].op == 'Q')
// cerr<<D.Q(Q[l].x)<<' '<<D.Q(Q[l].y)<<'\n',
cout<<1ll*D.Q(Q[l].x)*D.Q(Q[l].y)<<'\n';
}
else{
int mid = (l + r) >> 1;
qry(k<<1,l,mid);qry(k<<1|1,mid+1,r);
}
D.undo(top);
}
}T;
map<pii,int> mp;
inline void solve(){
cin>>n>>q;D.init(n);
rep(i,1,q,1){
cin>>Q[i].op>>Q[i].x>>Q[i].y;
if(Q[i].x > Q[i].y) swap(Q[i].x,Q[i].y);
pii p = mk(Q[i].x,Q[i].y);
if(Q[i].op == 'A') mp[p] = i;
else{
T.I(1,1,q,mp[p],i-1,p.first,p.second);
mp[p] = i+1;
}
}
for(auto i:mp) if(i.second <= q) T.I(1,1,q,i.second,q,i.first.first,i.first.second);
T.qry(1,1,q);
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
solve();
}
还是板子,不过变成了值域上分治。
考虑答案为\(w\),那么就等价于用\([0,w-1]\cup[w+1,\infty)\)的边可以形成一颗生成树。
然后将边权为\(w\)的边的出现区间设为\([0,w-1]\cup[w+1,\infty)\)即可。
点此查看代码
#include<bits/stdc++.h>
#include<bits/extc++.h>
// using namespace __gnu_pbds;
// using namespace __gnu_cxx;
using namespace std;
#define rep(i,s,t,p) for(int i = s;i <= t; i += p)
#define drep(i,s,t,p) for(int i = s;i >= t; i -= p)
#define Infile(x) freopen(#x".in","r",stdin)
#define Outfile(x) freopen(#x".out","w",stdout)
#define Ansfile(x) freopen(#x".ans","w",stdout)
#define Errfile(x) freopen(#x".err","w",stderr)
#ifdef LOCAL
FILE *InFile = Infile(in),*OutFile = Outfile(out);
// FILE *ErrFile = Errfile(err);
#else
FILE *InFile = stdin,*OutFile = stdout;
#endif
using ll=long long;using ull=unsigned long long;
using db = double;using ldb = long double;
const int N = 1e6 + 10,V = 1e5 + 10;
#define pii pair<int,int>
#define mk make_pair
#define eb emplace_back
struct DSU{
vector<int> fa,siz,sta;int tot;
inline int get_fa(int x){while(x != fa[x]) x = fa[x];return x;}
inline void init(int n){
fa.resize(n+1);siz.resize(n+1);
rep(i,1,n,1) fa[i] = i,siz[i] = 1;
tot = n;
}
inline void merge(int x,int y){
x = get_fa(x),y = get_fa(y);
if(x == y) return;
if(siz[x] > siz[y]) swap(x,y);
fa[x] = y,siz[y] += siz[x],sta.eb(x);
tot--;
}
inline void undo(){
int x = sta.back();sta.pop_back();
siz[fa[x]] -= siz[x];
fa[x] = x;tot++;
}
inline void undo(int res){while(sta.size() > res) undo();}
}D;
struct Segment_Tree_Divide{
vector<pii> t[V<<2];
void I(int k,int l,int r,int ql,int qr,int x,int y){
if(ql <= l && r <= qr) return t[k].eb(mk(x,y));
int mid = (l + r) >> 1;
if(ql <= mid) I(k<<1,l,mid,ql,qr,x,y);
if(qr > mid) I(k<<1|1,mid+1,r,ql,qr,x,y);
}
void qry(int k,int l,int r){
int bk = D.sta.size();
for(auto i:t[k]) D.merge(i.first,i.second);
if(l == r){
// cerr<<l<<": "<<D.tot<<'\n';
if(D.tot == 1) cout<<l<<'\n',exit(0);
else D.undo(bk);
return;
}
int mid = (l + r) >> 1;
qry(k<<1,l,mid);qry(k<<1|1,mid+1,r);
D.undo(bk);
}
}T;
int n,m,u[N<<1],v[N<<1],w[N<<1],mxv;
inline void solve(){
cin>>n>>m;D.init(n);
rep(i,1,m,1){
cin>>u[i]>>v[i]>>w[i];
mxv = max(mxv,w[i] + 1);
}
rep(i,1,m,1){
if(w[i]-1 >= 0) T.I(1,0,mxv,0,w[i]-1,u[i],v[i]);
T.I(1,0,mxv,w[i]+1,mxv,u[i],v[i]);
}
T.qry(1,0,mxv);
}
signed main(){
cin.tie(nullptr)->sync_with_stdio(false);
solve();
}
例题持续更新中……(说白了就是可能会咕)
标签:int,siz,线段,分治,笔记,fa,long,using,define From: https://www.cnblogs.com/hzoi-Cu/p/18487766