【线段树分治】(luoguP5787、p4585、cf938G)
洛谷P5787:https://www.luogu.com.cn/problem/P5787
题意:某条边 u v 会 在 l , r 时间段内存在,问每个时间点的图是不是二分图。
按照时间轴建树,首先对于修改操作,一个修改操作在线段树上操作会修改 logn 个节点的vector,最后再dfs遍历一整遍处理出询问。
关于判断二分图,其实并查集维护,判断是否有奇环即可。 dis[ u ] 记录 u 到 并查集 直接 父亲 的距离奇偶,然后并查集启发式合并,修改的只有fu的 父亲, fu 的 dis( 变成 dis[u] ^ dis[v] ^ 1), 以及 fv 的siz,Dis(u) 函数计算u到祖父亲的距离奇偶,计算的时候一直往上跑就好了。然后关于正确性:假如说u v 不在一个集合,那么把 fu 和 fv 并起来(也就是上文所说的操作),可以观察到计算Dis 值的时候是正确的。
假如说 u v 不在一个集合 , 那么u、v现在的并查集树绝对是树而没有环,假如 u v 之间有 一条非树边,构成的环一定是偶环,如果是奇环前面就会判断掉,那么u 不经过 非树边 和经过非树边到达 祖父亲的距离奇偶一定是一样的,所以不用保存非树边,也就是现在并查集是树。同样道理,u v 连非树边,假如与树边构成偶环,什么东西也不用变。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 3e5+9; 4 int cnt = 0; 5 vector< pair<int,int> > tr[N*4]; 6 pair<int,int > sta[N]; 7 int f[N],dis[N],siz[N],ans[N]; 8 int find(int x){ if(x == f[x]) return x; return find(f[x]);}; 9 int Dis(int x){ 10 int res = 0; 11 while(x != f[x]){ 12 res ^= dis[x]; 13 x = f[x]; 14 } 15 return res; 16 } 17 void merge(int u,int v){ 18 int fu = find(u) , fv = find(v); 19 if(siz[fu] > siz[fv]){ 20 swap(u,v); 21 swap(fu,fv); 22 } 23 f[ fu ] = fv; 24 dis[fu] = ( dis[u] ^ dis[v] ^ 1 ); 25 siz[fv] += siz[fu]; 26 sta[++cnt] = make_pair(fu,fv); 27 } 28 void add(int o,int l,int r,int x,int y,pair<int,int> w){ 29 if( x<= l && r <= y){ 30 tr[o].push_back(w); 31 return; 32 } 33 int m = (l+r)>>1; 34 if(x<=m) add(o<<1,l,m,x,y,w); 35 if( y > m) add(o<<1|1,m+1,r,x,y,w); 36 return; 37 } 38 void dfs(int o,int l,int r){ 39 int k = cnt; 40 bool ok = 1; 41 for(auto w : tr[o] ){ 42 int u = w.first,v = w.second; 43 int fu = find(u) , fv = find(v); 44 if( fu == fv ){ 45 if( (Dis(u) ^ Dis(v) ) == 0 ){ 46 ok = 0; 47 for(int i = l;i<=r;++i) ans[i] = 0; 48 break; 49 } 50 } 51 else merge(u,v); 52 } 53 if(ok){ 54 if(l==r) ans[l] = 1; 55 else{ 56 int m = (l+r)>>1; 57 dfs(o<<1,l,m); 58 dfs(o<<1|1,m+1,r); 59 } 60 } 61 while(cnt != k){ 62 int u = sta[cnt].first , v = sta[cnt].second; 63 f[u] = u; 64 dis[u] = 0; 65 siz[v] -= siz[u]; 66 --cnt; 67 } 68 } 69 int main(){ 70 int n,m,k; 71 scanf("%d %d %d",&n,&m,&k); 72 for(int i = 1;i<=m;++i){ 73 int u,v,l,r; scanf("%d %d %d %d",&u,&v,&l,&r); 74 if( l < r ) add(1,1,k,l+1,r,make_pair(u,v)); 75 } 76 for(int i = 1;i<=n;++i) f[i] = i , siz[i] = 1,dis[i] = 0; 77 dfs(1,1,k); 78 for(int i = 1;i<=n;++i){ 79 if(ans[i]) puts("Yes"); 80 else puts("No"); 81 } 82 return 0; 83 }
bzoj4311
题意:二维平面删除插入某些点,询问当前平面内的点与询问点最大的点积是多少。
先不考虑插入删除,平面上的n个点,只有上凸包是答案,然后我们考虑用线段树分治维护上凸包。我们首先对插入点按照x 轴排序,方便接下来的维护凸包,然后对于每个点,找到它对应的线段树上logN个区间节点,把这个点插入到这log个区间节点上(每个节点维护这一个上凸包),然后对于询问。重点来了!!!线段树分治的询问操作不是一定一成不变的直接遍历线段树,在这题当中是不可行的,因为直接遍历,遍历非根节点的时候我们不知到询问斜率是什么,并且在回溯这个撤销操作上也不太方便。
其实我们可以直接暴力!!!!直接枚举询问的叶子节点是哪一个,然后从根节点直接递归到叶子节点,中途节点的凸包三分一下找到答案,最后对所有答案取max即可。但是这题主要是每个区间节点可以三分来求,所以复杂度nloglog,但是其他题就不好说了。
bzoj现在还交不了题,挖个坑,日后补。
洛谷p4585:https://www.luogu.com.cn/problem/P4585
题意:有n个商店,m个操作,每个操作:1、当天在某个商店进了价值为x的商品。2、查询最近d天以内进货的,商店编号在l,r之间的商品 与 x 值异或的最大值。
我们可以用线段树分治来处理时间这一维,也就是先把询问的时间区间放到线段树上。然后我们在线段树上面建立可持久化trie来解决商品编号问题。
具体怎么解决,我们可以还是类似线段树分治的样子,遍历整个线段树,遍历到这个线段树节点,我们就重新构建可持久 trie,然后解决该节点的查询,然后对于我这个节点添加的信息,对时间进行分治,分别给左儿子以及右儿子,然后 一直下去。
正确性:询问拆成某几个线段树节点,也就是询问时间在这个节点区间内的点,然后我遍历整棵线段树的时候,添加的节点进行分治,递归到当前节点的vector,就是属于当前节点的添加,故正确。
时间复杂度:由于我每个添加点最多只插入log次,所以我总共插入nlog个节点进入可持久trie,然后我只开一个trie,每次清空直接trie.cnt = 0,重新构造即可。
感觉这题才是体现线段树分治的精髓。
1 #include<bits/stdc++.h> 2 using namespace std; 3 const int N = 1e5+9; 4 struct Que{ 5 int dl,dr,nl,nr,x; 6 int ans; 7 }; 8 vector<Que> q; 9 struct Add{ 10 int id,v; 11 int day; 12 bool operator < (Add& b)const{ 13 return id < b.id; 14 } 15 }; 16 struct Trie{ 17 int cnt; 18 int tr[N*30][2],sum[N*30]; 19 int insert(int las,int val){ 20 int tem,now; 21 tem = now = ++cnt; 22 for(int i = 23;i>=0;--i){ 23 tr[now][0] = tr[las][0]; 24 tr[now][1] = tr[las][1]; 25 sum[now] = sum[las] + 1; 26 bool t = (1<<i) & val; 27 las = tr[las][t]; 28 tr[now][t] = ++cnt; 29 now = tr[now][t]; 30 } 31 sum[now] = sum[las] + 1; 32 return tem; 33 } 34 int query(int l,int r,int val){ 35 int tem = 0; 36 for(int i = 23;i>=0;--i){ 37 bool t = (1<<i) & val; 38 if( sum[ tr[r][t^1] ] - sum[ tr[l][t^1] ] ){ 39 tem |= (1<<i); 40 r = tr[r][t^1]; 41 l = tr[l][t^1]; 42 } 43 else r = tr[r][t] , l = tr[l][t]; 44 } 45 return tem; 46 } 47 }trie; 48 vector<int> tr[N*4]; 49 int root[N]; 50 void update(int o,int l,int r,int x,int y,int id){ 51 // cerr<<l<<" "<<r<<" "<<x<<" "<<y<<endl; 52 if(x>y) return; 53 if( x<=l && r<=y){ 54 tr[o].push_back(id); 55 // cerr<<o<<" "<<l<<" "<<r<<" "<<id<<" id"<<endl; 56 return; 57 } 58 int m = (l+r)>>1; 59 if(x<=m) update(o<<1,l,m,x,y,id); 60 if(y>m) update(o<<1|1,m+1,r,x,y,id); 61 } 62 void calc(int o,vector<Add>& add){ 63 trie.cnt = 0; 64 vector<int> ins; 65 int n = 0; 66 for(auto it : add){ 67 ins.push_back(it.id); 68 ++n; 69 root[n] = trie.insert(root[n-1],it.v); 70 } 71 for(auto it : tr[o]){ 72 // cerr<<o<<" "<<it<<" oit"<<endl; 73 int l = q[it].nl , r = q[it].nr; 74 // cerr<<l<<" "<<r<<endl; 75 l = lower_bound(ins.begin(),ins.end(),l) - ins.begin() + 1; 76 r = upper_bound(ins.begin(),ins.end(),r) - ins.begin(); 77 // cerr<<o<<" "<<it<<" oit"<<l<<" "<<r<<endl; 78 q[it].ans = max(q[it].ans,trie.query(root[l-1],root[r],q[it].x)); 79 } 80 } 81 void dfs(int o,int l,int r,vector<Add>& add){ 82 if(add.size() == 0 ) return; 83 // cerr<<o<<" o"<<endl; 84 calc(o,add); 85 if(l==r) return; 86 int m = (l+r)>>1; 87 vector<Add> addl,addr; 88 for(auto it : add){ 89 // cerr<<o<<" o"<<it.id<<" "<<it.v<<endl; 90 if(it.day <= m) addl.push_back(it); 91 else addr.push_back(it); 92 } 93 dfs(o<<1,l,m,addl); 94 dfs(o<<1|1,m+1,r,addr); 95 } 96 int main(){ 97 int n,m; scanf("%d %d",&n,&m); 98 for(int i = 1;i<=n;++i){ 99 int a; scanf("%d",&a); 100 root[i] = trie.insert(root[i-1],a); 101 } 102 vector<Add> add; 103 for(int i = 1;i<=m;++i){ 104 int ty; scanf("%d",&ty); 105 if(ty == 0){ 106 int s,v; scanf("%d %d",&s,&v); 107 int cnt = add.size(); 108 add.push_back((Add){s,v,cnt+1}); 109 } 110 else{ 111 int l,r,x,d; scanf("%d %d %d %d",&l,&r,&x,&d); 112 int ans = trie.query(root[l-1],root[r],x); 113 q.push_back((Que){max(1,(int)add.size()-d+1),(int)add.size(),l,r,x,ans}); 114 } 115 } 116 // for(auto it : q) cerr<<it.dl<<" "<<it.dr<<" day"<<endl; 117 for(int i = 0;i<q.size();++i) update(1,1,(int)add.size(),q[i].dl,q[i].dr,i); 118 sort(add.begin(),add.end()); 119 dfs(1,1,add.size(),add); 120 for(auto it : q) printf("%d\n",it.ans); 121 return 0; 122 }
codeforces 938G:https://codeforces.com/contest/938/problem/G
题意:给你一张带边权无向图,m个操作,每个操作加边,删边,询问两个点之间的最短异或路径。
考虑线段树分治,对于加减边,边的存活肯定是某一段时间,把时间区间把它放到线段树上,就相当于某几个线段树节点要加某些边。 然后对于询问,单点询问,线段树分治的单点询问其实有两种做法,一种做法是先把询问遍历到 线段树叶子节点,然后加tag,另一种做法就是在遍历的时候维护一个询问的vector,然后边遍历边分治就好了。
这题询问路径异或,线性基可以处理一张图的两点的异或最大最小,其实就是把环丢进线性基。然后每一次回溯的时候用可撤销并查集维护就好了。就是线性基维护非树边,并查集维护树边。然后线性基撤销?不存在的,每个线段树节点开一个线性基好了。hhh
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 const int MN = 29; 5 const int N = 2e5+9; 6 struct Base{ 7 int a[32]; 8 Base(){memset(a,0,sizeof(a));} 9 void ins(int x){ 10 for(int i = MN;i>=0;--i){ 11 if( x & (1<<i) ){ 12 if(a[i]) x ^= a[i]; 13 else{ 14 a[i] = x; 15 break; 16 } 17 } 18 } 19 } 20 int qmin(int x){ 21 int res = x; 22 for(int i = MN;i>=0;--i) res = min(res,res ^ a[i]); 23 return res; 24 } 25 }base; 26 struct Edge{ 27 int u,v,w; 28 }; 29 struct Node{ 30 Base bas; 31 vector<Edge> add; 32 }tr[N<<2]; 33 struct Query{ 34 int u,v,id,tim; 35 }; 36 vector<Query> que; 37 int ans[N]; 38 int dis[N],f[N],siz[N],top = 0; 39 pair<int,int> sta[N]; 40 map< pair<int,int> , pair<int,int> > mp; 41 void add_edge(int o,int l,int r,int x,int y,Edge e){ 42 if(x<=l && r <= y){ 43 tr[o].add.push_back(e); 44 return; 45 } 46 int m = (l+r)>>1; 47 if(x<=m) add_edge(o<<1,l,m,x,y,e); 48 if(y>m) add_edge(o<<1|1,m+1,r,x,y,e); 49 } 50 int find(int x){ 51 if( x == f[x]) return x; 52 return find(f[x]); 53 } 54 int find_dis(int x){ 55 int res = 0; 56 while( x != f[x] ){ 57 res ^= dis[x]; 58 x = f[x]; 59 } 60 return res; 61 } 62 void merge(int u,int fu,int v,int fv,int w){ 63 if(siz[fu] > siz[fv] ){ 64 swap(u,v); 65 swap(fu,fv); 66 } 67 siz[fv] += siz[fu]; 68 f[fu] = fv; 69 dis[fu] = find_dis(u) ^ w ^ find_dis(v); 70 sta[++top] = make_pair(fu,fv); 71 } 72 void work(int o,vector<Query> q){ 73 for(auto it : tr[o].add){ 74 int u = it.u , v = it.v , w = it.w; 75 int fu = find(u) , fv = find(v); 76 if(fu == fv ) tr[o].bas.ins( find_dis(u) ^ find_dis(v) ^ w); 77 else merge(u,fu,v,fv,w); 78 } 79 } 80 void roll_back(int tag){ 81 while(top != tag){ 82 int fu = sta[top].first , fv = sta[top].second; 83 --top; 84 siz[fv] -= siz[fu]; 85 f[fu] = fu; 86 dis[fu] = 0; 87 } 88 } 89 void dfs(int o,int l,int r,vector<Query> q){ 90 int tag = top; 91 work(o,q); 92 if(l==r){ 93 for(auto it : q){ 94 int u = it.u, v = it.v,id= it.id; 95 int fu = find(u) , fv = find(v); 96 if(fu != fv) continue; 97 int tem = find_dis(u) ^ find_dis(v); 98 ans[id] = min(ans[id],tr[o].bas.qmin(tem)); 99 } 100 roll_back(tag); 101 return; 102 } 103 tr[o<<1].bas = tr[o].bas; 104 tr[o<<1|1].bas = tr[o].bas; 105 int m = (l+r)>>1; 106 vector<Query> ql,qr; 107 for(auto it : q){ 108 if(it.tim <= m ) ql.push_back(it); 109 else qr.push_back(it); 110 } 111 dfs(o<<1,l,m,ql); 112 dfs(o<<1|1,m+1,r,qr); 113 roll_back(tag); 114 } 115 int main(){ 116 int n,m; scanf("%d %d",&n,&m); 117 for(int i = 1;i<=n;++i) f[i] = i,siz[i] = 1; 118 memset(ans,127,sizeof(ans)); 119 for(int i = 1;i<=m;++i){ 120 int u,v,w; scanf("%d %d %d",&u,&v,&w); 121 if( u > v) swap(u,v); 122 mp[ make_pair(u,v) ] = make_pair(1,w); 123 } 124 int qn; scanf("%d",&qn); 125 int cnt = 0; 126 for(int i = 1;i<=qn;++i){ 127 int ty,x,y,d; scanf("%d %d %d",&ty,&x,&y); 128 if( x > y) swap(x,y); 129 if(ty == 1){ 130 scanf("%d",&d); 131 mp[ make_pair(x,y) ] = make_pair(i,d); 132 } 133 else if(ty == 2){ 134 int las = mp[ make_pair(x,y) ].first; 135 int w = mp[ make_pair(x,y) ].second; 136 add_edge(1,1,qn,las,i,(Edge){x,y,w}); 137 mp.erase( make_pair(x,y) ); 138 } 139 else{ 140 Query tem = (Query){x,y,++cnt,i}; 141 que.push_back(tem); 142 } 143 } 144 for(auto it : mp){ 145 int u = it.first.first , v = it.first.second; 146 int w = it.second.second; 147 int tim = it.second.first; 148 add_edge(1,1,qn,tim,qn,(Edge){u,v,w}); 149 } 150 dfs(1,1,qn,que); 151 // cerr<<cnt<<" !"<<endl; 152 for(int i = 1;i<=cnt;++i) printf("%d\n",ans[i]); 153 return 0; 154 }