线段树分治
咋这知识点也能丢
线段树分治(目前学到的)能解决形如操作对询问有区间影响的题目(并且要求操作可撤销),这种情况下,可以直接对询问建线段树,然后预处理出询问对哪些区间有影响,直接在线段树上区间修改(线段树节点上记录这个节点处的操作),注意这里需要修改的只有符合要求的最上层的点,修改完直接返回,后续也无需 pushdown
这么干的原因是我们在预处理完毕后会对线段树进行一次 DFS,模拟进行操作的过程,每次进入一个节点就进行节点存储的操作,退出时就撤销操作,每次遍历到叶节点时就记录当前答案到对应询问,由于父节点的操作会对子节点产生影响,因此无需将操作下传
进行能线段树分治的题目要求还是很严格的,首先需要保证操作可撤销,其次还要保证贡献能实时计算(或者以很低的复杂度算出来),否则跑线段树分治没什么优化,常见的一类题是连通块题,用可撤销并查集解决
先说一下可撤销并查集,并查集如果要可撤销,可以开一个栈记录每个并查集上合并操作的信息,撤销时以此弹栈到指定位置即可,由于需要撤销,因此不能路径压缩(否则没法还原祖先状态),为了保证复杂度需要按秩合并或启发式合并
也没啥可说的,就一个码力算法,直接上题吧
P5787 二分图 /【模板】线段树分治
学的时候没看知识点讲解,直接看了大体思路就开始硬莽,所以写的时候踩了线段树分治大部分的坑,除了题目特有的坑之外,比较常见的就是并查集在某个位置忘记撤销,包括但不限于叶节点返回处,判非法后返回处等
还有一个比较需要注意的是可持久化并查集的撤销操作那里比较易错,在线段树分治里用,由于撤销时,添加的操作一定在栈顶,因此可以用 st.top().id==id 来判,但是实际上可撤销并查集不应该怎么写,应该给编号一个单调性,然后根据单调性判
注意这里是可以撤销的条件,不是终止条件,不止一次写了 st.top().id!=id,可能是我比较菜
#include<bits/stdc++.h>
using namespace std;
int n,m,k;
struct edge{
int from,to;
}ed[400001];
vector<int>e[400001];
#define tol (id*2)
#define tor (id*2+1)
void change(int id,int l,int r,int L,int R,int edgeid){
if(l>R or r<L) return;
if(L<=l and r<=R){
e[id].push_back(edgeid);
return;
}
int mid=(l+r)/2;
if(R<=mid) change(tol,l,mid,L,R,edgeid);
else if(L>=mid+1) change(tor,mid+1,r,L,R,edgeid);
else{
change(tol,l,mid,L,mid,edgeid);
change(tor,mid+1,r,mid+1,R,edgeid);
}
}
struct exdsu{
struct ope{
int id;
int merged,mergedsize;
};
int fa[400001];
int size[400001];
stack<ope>st;
void clear(int n){
while(!st.empty()) st.pop();
for(int i=1;i<=n;++i){
fa[i]=i;
size[i]=1;
}
}
int find(int id){
if(id==fa[id]) return id;
return find(fa[id]);
}
bool isconnect(int x,int y){
return find(x)==find(y);
}
bool join(int x,int y,int id){
// cout<<"merge "<<x<<" "<<y<<endl;
int fx=find(x),fy=find(y);
if(fx==fy) return false;
if(size[fx]<size[fy]) swap(fx,fy);
st.push({id,fy,size[fy]});
fa[fy]=fx;
size[fx]+=size[fy];
size[fy]=0;
return true;
}
void undo_before(int id){
while((!st.empty()) and st.top().id==id){
ope a=st.top();st.pop();
size[fa[a.merged]]-=a.mergedsize;
fa[a.merged]=a.merged;
size[a.merged]=a.mergedsize;
}
}
}d;
bool ans[400001];
void dfs(int id,int l,int r){
for(int i:e[id]){
if(d.find(ed[i].from)==d.find(ed[i].to)){
for(int j=l;j<=r;++j){
ans[j]=0;
}
d.undo_before(id);
return;
}
if(!d.join(ed[i].from,ed[i].to+n,id));
if(!d.join(ed[i].from+n,ed[i].to,id));
}
// cout<<endl;
if(l==r){
ans[l]=true;
d.undo_before(id);
return;
}
int mid=(l+r)/2;
dfs(tol,l,mid);
dfs(tor,mid+1,r);
d.undo_before(id);
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m>>k;
for(int i=1;i<=m;++i){
int l,r;
cin>>ed[i].from>>ed[i].to>>l>>r;l++;
change(1,1,k,l,r,i);
}
d.clear(2*n);
dfs(1,1,k);
for(int i=1;i<=k;++i){
cout<<(ans[i]?"Yes":"No")<<'\n';
}
}
异或连通
按照线段树分治的思路直接做
这道题的难点在找到操作会影响哪些区间,上个结论
操作影响的区间一定形如若干连续段,且连续段不超过 \(\log\) 个
证明:考虑拆位,操作 \(s\) 满足 \(s\operatorname{xor}x\lt K\),当且仅当存在某一位 \(i\),使得对于任意更高位 \(j\),\(s_j\operatorname{xor}x_j=K_j\),且 \(s_i\operatorname{xor}x_i=1,K_i=0\)
如果我们对询问建一颗 Trie 树,符合要求的询问一定对应 Trie 上某个子树,如果我们事先对询问排序,那么对应的就是一个连续的区间了,由于 Trie 深度最高 \(\log\),因此连续段也不超过 \(\log\) 个
因此,本题的方案就是:对询问建立 Trie 树,找到询问区间,然后直接线段树分治即可
#include<bits/stdc++.h>
using namespace std;
#ifndef IOEXTEND_H
#define IOEXTEND_H
#include<bits/stdc++.h>
using namespace std;
namespace __coutf{struct format_error{format_error(){}};}
template<char replace_type='%'>
void coutf(const string &x){
for(char i:x) if(i==replace_type) throw __coutf::format_error();
cout<<x;cout.flush();
}
template<char replace_type='%',typename T,typename...Args>
void coutf(const string&x,const T &t,const Args&...args){
if(x.empty()) throw __coutf::format_error();
string ret,ret2;int i=0;
for(;i<=(int)x.length()-1;++i){
if(x[i]==replace_type) break;
ret.push_back(x[i]);
}
bool flag=true;
if(x[i]!=replace_type) flag=false;
for(++i;i<=(int)x.length()-1;++i){
ret2.push_back(x[i]);
}
cout<<ret;
if(!flag) throw __coutf::format_error();
cout<<t;
coutf<replace_type>(ret2,args...);
}
#endif
#define int long long
int n,m,Q,K;
struct edge{
int from,to,w;
};
struct ques{
signed id;
int x;
bool operator<(const ques&A)const{
return x<A.x;
}
};
ques q[100001];
struct tree{
vector<signed>e;
}t[100001*32];
#define tol (id*2)
#define tor (id*2+1)
#define mid(l,r) mid=((l)+(r))/2
void change(int id,int l,int r,int L,int R,int edgeid){
assert(L<=R);
if(L<=l and r<=R){
t[id].e.push_back(edgeid);
return;
}
int mid(l,r);
if(R<=mid) change(tol,l,mid,L,R,edgeid);
else if(L>=mid+1) change(tor,mid+1,r,L,R,edgeid);
else{
change(tol,l,mid,L,mid,edgeid);
change(tor,mid+1,r,mid+1,R,edgeid);
}
}
struct trie{
signed to[100001*32][2];
int tot[100001*32];
int size[100001*32];
int cnt=0;
void insert(int x){
int pos=0;
for(int i=30;i>=0;--i){
if(!to[pos][(x>>i)&1]){
to[pos][(x>>i)&1]=++cnt;
}
pos=to[pos][(x>>i)&1];
}
tot[pos]++;
}
void calsize(int pos){
for(int i:{0,1}){
if(to[pos][i]){
calsize(to[pos][i]);
size[pos]+=size[to[pos][i]];
}
}
size[pos]+=tot[pos];
// assert(size[pos]>=size[to[pos][0]] and size[pos]>=size[to[pos][1]]);
}
/*
,\mjbd
<K 写成 >=K 了
mjbdcnmwzsrlnmdglmlgbd
qwertyuiopasdfghjklzxcvbnm
*/
void cal(int val,int edgeid){ //calc all the x that val^x<K
int pos=0,staid=1;
for(int i=30;i>=0;--i){
// coutf("cal %(edge=%) : %; pos=%; staid=%\n",val,edgeid,i,pos,staid);
if((K>>i)&1){
if((val>>i)&1){ //1^0=1 -> dfs
if(to[pos][0]){
pos=to[pos][0];
}
else return;
}
else{ //0^1=1 -> dfs
if(to[pos][1]){
if(to[pos][0]){
staid+=size[to[pos][0]];
}
pos=to[pos][1];
}
else return;
}
}
else{
if((val>>i)&1){ //1^1=0 -> dfs | 1^0=1 -> change
if(to[pos][0]){
// coutf("change [%(staid) %(%+%-1)]\n",staid,staid+size[to[pos][0]]-1,staid,+size[to[pos][0]]);
change(1,1,Q,staid,staid+size[to[pos][0]]-1,edgeid);
}
if(to[pos][1]){
if(to[pos][0]){
staid+=size[to[pos][0]];
}
pos=to[pos][1];
}
else return;
}
else{ //0^0=0 -> dfs | 0^1=1 -> change
if(to[pos][1]){
// coutf("size[to[%][0](%)]=%,size[%]=%\n",pos,to[pos][0],size[to[pos][0]],pos,size[pos]);
// assert(size[to[pos][0]]<=size[pos]);
// coutf("change [%(%+%),%(%+%-1)]\n",staid+(to[pos][0]==0?0:size[to[pos][0]]),staid,size[to[pos][0]],staid+size[pos]-1,staid,size[pos]);
change(1,1,Q,staid+(to[pos][0]==0?0:size[to[pos][0]]),staid+size[pos]-1,edgeid);
}
if(to[pos][0]){
pos=to[pos][0];
}
else return;
}
}
}
}
void cal2(int val,int edgeid){
int pos=0,staid=1;
for(int i=30;i>=0;--i){
if((K>>i)&1){
if((val>>i)&1){ //1^0=1 dfs | 1^1=0 change
if(to[pos][1]){
change(1,1,Q,staid+(to[pos][0]==0?0:size[to[pos][0]]),staid+size[pos]-1,edgeid);
}
if(to[pos][0]){
pos=to[pos][0];
}
else return;
}
else{ //0^0=0 change | 0^1=1 dfs
if(to[pos][0]){
change(1,1,Q,staid,staid+size[to[pos][0]]-1,edgeid);
}
if(to[pos][1]){
if(to[pos][0]){
staid+=size[to[pos][0]];
}
pos=to[pos][1];
}
else return;
}
}
else{
if((val>>i)&1){ //1^0=1 return | 1^1=0 dfs
if(to[pos][1]){
if(to[pos][0]){
staid+=size[to[pos][0]];
}
pos=to[pos][1];
}
else return;
}
else{ //0^0=0 dfs | 0^1=1 return
if(to[pos][0]){
pos=to[pos][0];
}
else return;
}
}
}
}
}tr;
vector<edge>e={{}};
struct exdsu{
signed fa[100001];
int size[100001];
int ans;
struct ques{
int id;
int merged,mergesize;
};
stack<ques>st;
void clear(int n){
while(!st.empty()) st.pop();
for(int i=1;i<=n;++i){
fa[i]=i;
size[i]=1;
}
ans=0;
}
int find(int id){
if(id==fa[id]) return id;
return find(fa[id]);
}
inline int cal(int x){
return max(0ll,x*(x-1)/2);
}
void join(int x,int y,int id){
int fx=find(x),fy=find(y);
if(fx==fy) return;
// cout<<"join "<<x<<" "<<y<<endl;
if(size[fx]<size[fy]) swap(fx,fy);
ans-=cal(size[fx])+cal(size[fy]);
st.push({id,fy,size[fy]});
fa[fy]=fx;
size[fx]+=size[fy];
size[fy]=0;
ans+=cal(size[fx]);
}
void undo_before(int id){
while(!st.empty() and st.top().id==id){
auto a=st.top();
st.pop();
// cout<<"unjoin "<<a.merged<<" "<<fa[a.merged]<<endl;
ans-=cal(size[fa[a.merged]]);
ans+=cal(size[fa[a.merged]]-a.mergesize)+cal(a.mergesize);
size[fa[a.merged]]-=a.mergesize;
fa[a.merged]=a.merged;
size[a.merged]=a.mergesize;
}
}
}d;
int ans[100001];
void dfs(int id,int l,int r){
// cout<<"dfs "<<id<<" "<<l<<" "<<r<<endl;
// coutf("%[%,%]:\n",id,l,r);
for(int i:t[id].e){
// coutf("(%,%)\n",e[i].from,e[i].to);
d.join(e[i].from,e[i].to,id);
}
// cout<<endl;
if(l==r){
ans[q[l].id]=d.ans;
d.undo_before(id);
return;
}
int mid(l,r);
dfs(tol,l,mid);
dfs(tor,mid+1,r);
d.undo_before(id);
}
signed main(){
freopen("xor.in","r",stdin);
freopen("xor.out","w",stdout);
// freopen("down/xor/ex_xor2.in","r",stdin);
// freopen("test_Data_a_1731930531/test_12.in","r",stdin);
// freopen("test_Data_a_1731930531/test_14.in","r",stdin);
// freopen("test_Data_a_1731930531/test_49.in","r",stdin);
// freopen("test_Data_a_1731930531/test_52.in","r",stdin);
// freopen("out.out","w",stdout);
ios::sync_with_stdio(false);
cin>>n>>m>>Q>>K;
for(int i=1;i<=m;++i){
int x,y,z;
cin>>x>>y>>z;
e.push_back({x,y,z});
}
for(int i=1;i<=Q;++i){
cin>>q[i].x;
tr.insert(q[i].x);
q[i].id=i;
}
sort(q+1,q+Q+1);
// cout<<"???"<<endl;
// for(int i=1;i<=Q;++i){
// coutf("q%: ",i);
// for(edge j:e){
// if(((q[i].x^j.w)<K) and j.from!=0){
// coutf("(%,%) ",j.from,j.to);
// }
// }
// coutf("\n");
// }
tr.calsize(0);
// cout<<"rrr"<<endl;
for(int i=1;i<=(int)e.size()-1;++i){
tr.cal2(e[i].w,i);
}
// cout<<"<MMMM"<<endl;
d.clear(n);
dfs(1,1,Q);
for(int i=1;i<=Q;++i){
cout<<ans[i]<<'\n';
}
}
线段树分治很容易写锅,也可能是自己码力不足吧,但是好歹是调出来了