又扯回这道经典的可修改区间第K小值 Dynamic Ranking了:
http://www.lydsy.com:808/JudgeOnline/problem.php?id=1901
一、树套树
之前写过,挺慢的,也挺长的。
二、树状数组套权值线段树
如果没有修改,可以直接开n棵权值线段树,每一棵线段树都是在前一棵的基础上只修改一位。
每一棵线段树也都是每一个前缀所代表的“权值线段树”。
所以两棵线段树相减代表的就是一个区间的"权值线段树",就能在上面二分。
但是有修改了,每一次修改就会影响它后面的所有线段树。
所以使用BIT来维护这个前缀和,每次只会修改O(logn)棵,就没问题了。
每个区间也通过O(logn)棵线段树的加加减减得到。
现在来计算时间复杂度:
每次修改影响O(logn)课线段树,每棵线段树修改需要影响O(logn)个节点,所以是O(log^2n)的。
而线段树的节点也可以动态的开,每次修改需要的空间也是O(log^2n)级别的。
Code1:
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <queue> #include <string> #include <set> #include <vector> #include <map> using namespace std; #define N 10005 #define M 20005 int n , m , a[N]; int d[M] , D; pair<int , int> w[N]; int r[N]; int c[N]; #define Node 2097152 #define MID int mid = l + r >> 1 #define Left l , mid #define Right mid + 1 , r int ch[Node][2] , sum[Node] , nodecnt; int newnode() { ++ nodecnt; ch[nodecnt][0] = ch[nodecnt][1] = sum[nodecnt] = 0; return nodecnt; } void add(int& p , int l , int r , int x , int w) { if (!p) p = newnode(); if (l == r) sum[p] += w; else { MID; if (x <= mid) add(ch[p][0] , Left , x , w); else add(ch[p][1] , Right , x , w); sum[p] = sum[ch[p][0]] + sum[ch[p][1]]; } } void add(int x , int w , int val) { for (int i = x ; i <= n ; i += i & -i) add(c[i] , 1 , D , w , val); } int PP[20] , MM[20] , sp , sm; int query(int l , int r , int K) { if (l == r) return l; MID; int cnt = 0; for (int i = 0 ; i < sp ; ++ i) cnt += sum[ch[PP[i]][0]]; for (int i = 0 ; i < sm ; ++ i) cnt -= sum[ch[MM[i]][0]]; if (cnt >= K) { for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][0]; for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][0]; return query(Left , K); } else { for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][1]; for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][1]; return query(Right , K - cnt); } } void work() { int i , j , k , x , y; char str[5]; scanf("%d%d",&n,&m); for (i = 1 ; i <= n ; ++ i) scanf("%d",&a[i]) , d[D ++] = a[i]; for (i = 1 ; i <= m ; ++ i) { scanf("%s%d%d",str,&x,&y); if (*str == 'Q') scanf("%d",&k); else k = 0 , d[D ++] = y; w[i] = make_pair(x , y) , r[i] = k; } sort(d , d + D) , D = unique(d , d + D) - d; for (i = 1 ; i <= n ; ++ i) { a[i] = lower_bound(d , d + D , a[i]) - d + 1; add(i , a[i] , 1); } for (i = 1 ; i <= m ; ++ i) { x = w[i].first , y = w[i].second; if (r[i]) { sp = 0 ; for (j = y ; j ; j -= j & -j) PP[sp ++] = c[j]; sm = 0 ; for (j = x-1 ; j ; j -= j & -j) MM[sm ++] = c[j]; printf("%d\n" , d[query(1 , D , r[i]) - 1]); } else { y = lower_bound(d , d + D , y) - d + 1; add(x , a[x] , -1); a[x] = y; add(x , a[x] , 1); } } } int main() { work(); return 0; }
以上代码是离散化的,但如果强制在线就无法对新修改的权值进行离散化。
但实际上由于线段树的节点是完全动态开的,它的range完全可以设为数字的值域。
这样只会增加线段树的时空常数,从O(logn)变为O(log(range)),能简化不少代码。
但空间是很宝贵的……能离散化的时候尽量离散化吧。
#include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <algorithm> #include <queue> #include <string> #include <set> #include <vector> #include <map> using namespace std; #define N 10005 int n , m , a[N]; int D = 1e9; int c[N]; #define Node 8388608 #define MID int mid = l + r >> 1 #define Left l , mid #define Right mid + 1 , r int ch[Node][2] , sum[Node] , nodecnt; int newnode() { ++ nodecnt; ch[nodecnt][0] = ch[nodecnt][1] = sum[nodecnt] = 0; return nodecnt; } void add(int& p , int l , int r , int x , int w) { if (!p) p = newnode(); if (l == r) sum[p] += w; else { MID; if (x <= mid) add(ch[p][0] , Left , x , w); else add(ch[p][1] , Right , x , w); sum[p] = sum[ch[p][0]] + sum[ch[p][1]]; } } void add(int x , int w , int val) { for (int i = x ; i <= n ; i += i & -i) add(c[i] , 0 , D , w , val); } int PP[50] , MM[50] , sp , sm; int query(int l , int r , int K) { if (l == r) return l; MID; int cnt = 0; for (int i = 0 ; i < sp ; ++ i) cnt += sum[ch[PP[i]][0]]; for (int i = 0 ; i < sm ; ++ i) cnt -= sum[ch[MM[i]][0]]; if (cnt >= K) { for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][0]; for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][0]; return query(Left , K); } else { for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][1]; for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][1]; return query(Right , K - cnt); } } void work() { int i , j , k , x , y; char str[5]; scanf("%d%d",&n,&m); for (i = 1 ; i <= n ; ++ i) scanf("%d",&a[i]) , add(i , a[i] , 1); for (i = 1 ; i <= m ; ++ i) { scanf("%s%d%d",str,&x,&y); if (*str == 'Q') { scanf("%d",&k); sp = 0 ; for (j = y ; j ; j -= j & -j) PP[sp ++] = c[j]; sm = 0 ; for (j = x-1 ; j ; j -= j & -j) MM[sm ++] = c[j]; printf("%d\n" , query(0 , D , k)); } else { add(x , a[x] , -1); a[x] = y; add(x , a[x] , 1); } } } int main() { work(); return 0; }
Query过程写的比较暴力随性……怎么方便怎么来……
然后今天推翻一切自己重写才发现出乎意料地好想好写……原来写的那是什么玩意啊 - -
http://www.lydsy.com:808/JudgeOnline/problem.php?id=1146
前几天刚写完暴力的树链剖分线段树套平衡树 = =
修改查询的复杂度分别是O(log^2n)和O(log^4n),很吓人。
也能使用可持久化线段树卖空间来提高时间效率……
每棵线段树记录的是它到根所有经过的节点的权值。
这样一条从x到y的链就可以用X+Y-LCA(x,y)-father(LCA(x,y))得到(这里是点权)。
考虑修改,修改一个点权影响的是它的子树,考虑DFS序列就是一个区间。
所以使用另外一棵线段树来维护DFS序列,实现区间增和单点查询,这个是很好做的,也不用增加标记。
每一条从根出发的链的"权值线段树"都能利用这棵线段树,成为O(logn)棵"权值线段树"的和,对"权值线段树"查询修改也需要O(logn)时间。
询问和修改的时空复杂度就也是O(log^2n)级别的。
这道题空间非常紧……需要卡好空间限制才能过,如果不先离散化的话。
Code:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #pragma comment(linker,"/STACK:102400000,102400000") using namespace std; #define N 80005 int n , m , a[N] , pre[N] , mcnt; struct edge { int x , next; }e[N << 1]; int dep[N] , f[17][N] , L[N] , R[N] , ncnt; void dfs(int x , int fa) { f[0][x] = fa , dep[x] = dep[fa] + 1 , L[x] = ++ ncnt; for (int i = pre[x] ; ~i ; i = e[i].next) if (e[i].x != fa) dfs(e[i].x , x); R[x] = ncnt; } int LCA(int x , int y) { if (dep[x] < dep[y]) swap(x , y); for (int i = 16 ; i >= 0 ; -- i) if (dep[x] - dep[y] >> i & 1) x = f[i][x]; if (x == y) return y; for (int i = 16 ; i >= 0 ; -- i) if (f[i][x] != f[i][y]) x = f[i][x] , y = f[i][y]; return f[0][x]; } int Down = -1 , Up = 1e8 , t[N << 1]; #define Node 12582912 int id(int l , int r) {return l + r | l != r;} #define MID int mid = l + r >> 1 #define Left l , mid #define Right mid + 1 , r int ch[Node][2] , sum[Node] , nodecnt; int newnode() { ++ nodecnt; ch[nodecnt][0] = ch[nodecnt][1] = sum[nodecnt] = 0; return nodecnt; } void add(int& p , int l , int r , int x , int w) { if (!p) p = newnode(); if (l == r) sum[p] += w; else { MID; if (x <= mid) add(ch[p][0] , Left , x , w); else add(ch[p][1] , Right , x , w); sum[p] = sum[ch[p][0]] + sum[ch[p][1]]; } } int PP[100] , MM[100] , sp , sm; int query(int l , int r , int K) { if (l == r) return l; MID; int cnt = 0; for (int i = 0 ; i < sp ; ++ i) cnt += sum[ch[PP[i]][1]]; for (int i = 0 ; i < sm ; ++ i) cnt -= sum[ch[MM[i]][1]]; if (cnt >= K) { for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][1]; for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][1]; return query(Right , K); } else { for (int i = 0 ; i < sp ; ++ i) PP[i] = ch[PP[i]][0]; for (int i = 0 ; i < sm ; ++ i) MM[i] = ch[MM[i]][0]; return query(Left , K - cnt); } } void ADD(int l , int r , int top , int bot , int x , int w) { int p = id(l , r); if (top <= l && r <= bot) add(t[p] , Down , Up , x , w); else { MID; if (top <= mid) ADD(Left , top , bot , x , w); if (bot > mid) ADD(Right , top , bot , x , w); } } void QUERY(int l , int r , int x , bool flag) { int p = id(l , r); if (flag) PP[sp ++] = t[p]; else MM[sm ++] = t[p]; if (l != r) { MID; if (x <= mid) QUERY(Left , x , flag); else QUERY(Right , x , flag); } } void work() { int i , j , k , x , y , z; scanf("%d%d",&n,&m); for (i = 1 ; i <= n ; ++ i) scanf("%d",&a[i]); memset(pre , -1 , sizeof(pre)) , mcnt = 0; for (i = 1 ; i < n ; ++ i) { scanf("%d%d",&x,&y); e[mcnt] = (edge) {y , pre[x]} , pre[x] = mcnt ++; e[mcnt] = (edge) {x , pre[y]} , pre[y] = mcnt ++; } dfs(1 , 0); for (j = 1 ; 1 << j <= n ; ++ j) for (i = 1 ; i <= n ; ++ i) f[j][i] = f[j - 1][f[j - 1][i]]; for (i = 1 ; i <= n ; ++ i) ADD(1 , n , L[i] , R[i] , a[i] , 1); for (i = 1 ; i <= m ; ++ i) { scanf("%d%d%d",&k,&x,&y); if (k) { sp = sm = 0 , z = LCA(x , y); QUERY(1 , n , L[x] , 1); QUERY(1 , n , L[y] , 1); QUERY(1 , n , L[z] , 0); if(f[0][z]) QUERY(1 , n , L[f[0][z]] , 0); k = query(Down , Up , k); if (~k) printf("%d\n" , k); else puts("invalid request!"); } else { ADD(1 , n , L[x] , R[x] , a[x] , -1); a[x] = y; ADD(1 , n , L[x] , R[x] , a[x] , 1); } } } int main() { work(); return 0; }