• 树套树总结


    最近做题发现自己并不知道什么时候该用树套树,就来总结一下

    一、静态整体kth

    排序输出

    	sort(a+1,a+n+1);
    	printf("%d
    ",a[k]); 
    

    时间复杂度O(nlogn) 空间复杂度O(n)

    二、动态整体kth

    权值线段树+二分

    查询时先查询左子树和sum,比较k和sum的大小:若k<=sum则说明第k小数在左子树中,递归查询左子树;

    否则,这个数对应的就是右子树中第k-sum小的数,k-=sum,递归查询右子树。

    时间复杂度O(nlogn) 空间复杂度O(n)

    应该是这么写的吧……甚至离散化刚开始都写错了

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    int read(){
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 1e5+10;
    int sum[maxn << 1];
    int ls(int x){return x << 1;}
    int rs(int x){return x << 1 | 1;} 
    void modify(int x,int l,int r,int p,int k){
    	sum[x] += k;
    	if (l == r) return;
    	int mid = (l+r >> 1);
    	if (p <= mid) modify(ls(x),l,mid,p,k);
    	else modify(rs(x),mid+1,r,p,k);
    } 
    int query(int x,int l,int r,int k){
    //	cout<<l<<" "<<r<<" "<<sum[ls(x)]<<" "<<k<<endl;
    	int mid = (l+r >> 1);
    	if (l == r) return l;
    	if (sum[ls(x)] >= k) return query(ls(x),l,mid,k);
    	else return query(rs(x),mid+1,r,k-sum[ls(x)]);
    }
    int n,m;
    int a[maxn],b[maxn];
    int main(){
    	n = read(),m = read();
    	for (int i = 1;i <= n;i++) a[i] = b[i] = read();
    	sort(b+1,b+n+1);
    	int len = unique(b+1,b+n+1)-b-1;
    	for (int i = 1;i <= n;i++) a[i] = lower_bound(b+1,b+len+1,a[i])-b;
    	for (int i = 1;i <= n;i++) modify(1,1,len,a[i],1);
    	for (int i = 1;i <= m;i++){
    		int op = read(); 
    		if (op == 0){
    			int x = read(),k = read();
    			modify(1,1,len,a[x],-1);
    			a[x] = k;
    			modify(1,1,len,a[x],1);
    		}
    		if (op == 1){
    			int k = read();
    			printf("%d
    ",b[query(1,1,len,k)]);
    		}
    	}
    	return 0;
    }
    

    三、静态区间kth

    对每个点以其前缀开一棵权值线段树,那么任意一段区间均可以表示成为两棵权值线段树作差,即R位置的线段树减去L-1位置上的线段树

    每个点开一棵线段树空间复杂度(O(n^2)),MLE,考虑到后一个位置相比于前一个位置的更改只有logn个节点,所以使用主席树

    时间复杂度O(nlogn) 空间复杂度O(nlogn)

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    int read(){
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 1e5+10;
    int sum[maxn << 1];
    int tot,root[maxn];
    struct node{
    	int ls,rs,val;
    }tree[maxn*30];
    void modify(int &now,int lst,int l,int r,int p,int k){
    	if (!now) now = ++tot;
    	tree[now].val = tree[lst].val + k;
    	if (l == r) return;
    	int mid = (l+r >> 1);
    	if (p <= mid) tree[now].rs = tree[lst].rs,modify(tree[now].ls,tree[lst].ls,l,mid,p,k);
    	else tree[now].ls = tree[lst].ls,modify(tree[now].rs,tree[lst].rs,mid+1,r,p,k); 
    } 
    int query(int now,int lst,int l,int r,int k){
    	if (!now) return 0;
    	int mid = (l+r >> 1);
    	if (l == r) return l;
    	int res = tree[tree[now].ls].val-tree[tree[lst].ls].val;	
    	if (res >= k) return query(tree[now].ls,tree[lst].ls,l,mid,k);
    	else return query(tree[now].rs,tree[lst].rs,mid+1,r,k-res);
    }
    int n,m;
    int a[maxn],b[maxn];
    int main(){
    	n = read(),m = read();
    	for (int i = 1;i <= n;i++) a[i] = b[i] = read();
    	sort(b+1,b+n+1);
    	int len = unique(b+1,b+n+1)-b-1;
    	for (int i = 1;i <= n;i++) a[i] = lower_bound(b+1,b+len+1,a[i])-b;
    	for (int i = 1;i <= n;i++) modify(root[i],root[i-1],1,len,a[i],1);
    	for (int i = 1;i <= m;i++){
    		int l = read(),r = read(),k = read();
    		printf("%d
    ",b[query(root[r],root[l-1],1,len,k)]);
    	}
    	return 0;
    }
    

    四、动态区间kth

    还是要想办法维护前缀和。如果只是同3的前缀和的话,就要对前缀和进行O(nlogn)的单次修改,显然TLE。

    这里考虑用树状数组维护前缀和。修改时,可以只修改logn个位置,复杂度(O(log^2n))

    查询时,依旧是R位置减去L-1位置,这时候不再是两棵线段树作差,而是log棵线段树与log棵线段树作差;跳的时候,log个节点一起跳到左子树/右子树

    时间复杂度(O(nlog^2n)) 空间复杂度O(nlogn)

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    int read(){
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 1e5+10;
    int n,m;
    int a[maxn],b[maxn];
    int ql[maxn],qr[maxn],qk[maxn];
    struct node{
    	int ls,rs,sum;
    }tree[maxn*30];
    int tot,a1[maxn],a2[maxn],cnt1,cnt2;
    void add(int &x,int l,int r,int p,int k){
    	if (!x) x = ++tot;
    	tree[x].sum += k;
    	int mid = (l+r >> 1);
    	if (l == r) return;
    	if (p <= mid) add(tree[x].ls,l,mid,p,k);
    	else add(tree[x].rs,mid+1,r,p,k);
    }
    int root[maxn],len;
    void build(int x,int k){for (int i = x;i <= n;i += i&-i) add(root[i],1,len,a[x],k);}
    int getkth(int l,int r,int k){
    	int res = 0,mid = (l+r >> 1);
    	if (l == r) return l;
    	for (int i = 1;i <= cnt1;i++) res += tree[tree[a1[i]].ls].sum;
    	for (int i = 1;i <= cnt2;i++) res -= tree[tree[a2[i]].ls].sum;
    	if (res >= k){
    		for (int i = 1;i <= cnt1;i++) a1[i] = tree[a1[i]].ls;
    		for (int i = 1;i <= cnt2;i++) a2[i] = tree[a2[i]].ls;
    		return getkth(l,mid,k);
    	}
    	else{
    		for (int i = 1;i <= cnt1;i++) a1[i] = tree[a1[i]].rs;
    		for (int i = 1;i <= cnt2;i++) a2[i] = tree[a2[i]].rs;
    		return getkth(mid+1,r,k-res);
    	}
    }
    int query(int l,int r,int k){
    	cnt1 = 0,cnt2 = 0;
    	for (int i = r;i;i -= i&-i) a1[++cnt1] = root[i];
    	for (int i = l-1;i;i -= i&-i) a2[++cnt2] = root[i];
    	return getkth(1,len,k);
    }
    int cnt;
    int main(){
    	n = read(),m = read();
    	for (int i = 1;i <= n;i++) a[i] = b[++cnt] = read();
    	for (int i = 1;i <= m;i++){
    		char op[10];scanf ("%s",op);
    		if (op[0] == 'Q') ql[i] = read(),qr[i] = read(),qk[i] = read();
    		if (op[0] == 'C') ql[i] = read(),qk[i] = b[++cnt] = read();
    	}
    	sort(b+1,b+cnt+1);
    	len = unique(b+1,b+cnt+1)-b-1;
    	for (int i = 1;i <= n;i++) a[i] = lower_bound(b+1,b+len+1,a[i])-b;
    	for (int i = 1;i <= n;i++) if (!qr[i]) qk[i] = lower_bound(b+1,b+len+1,qk[i])-b; 
    	for (int i = 1;i <= n;i++) build(i,1);
    	for (int i = 1;i <= m;i++){
    		if (qr[i]) printf("%d
    ",b[query(ql[i],qr[i],qk[i])]);
    		else build(ql[i],-1),a[ql[i]] = qk[i],build(ql[i],1);
    	} 
    	return 0;
    }
    
  • 相关阅读:
    RMAN备份脚本执行遇到RMAN-03002,06091问题处理
    物化视图日志过大,手工清理
    大表添加一个字段需求
    oracle_job进程相关学习测试
    11.2.0.4单实例静默安装
    RMAN执行crosscheck archive报错ORA-19633问题处理
    df执行hang住
    应用人员反馈报错,ORA-03137: TTC protocol internal error : [12333]
    普通表分区改造_rename方式
    SQL查询oracle数据库最近备份情况
  • 原文地址:https://www.cnblogs.com/little-uu/p/14878467.html
Copyright © 2020-2023  润新知