• 「BZOJ3065」带插入区间第K小值 替罪羊树×线段树


    题目描述

    从前有(n)只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力(a_i)。跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴。这时跳蚤国王决定理性愉悦一下,查询区间(k)小值。他每次向它的随从伏特提出这样的问题: 从左往右第(x)个到第(y)个跳蚤中,a[i]第(k)小的值是多少。

    这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王
    的询问。

    这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少。

    这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问。

    这时伏特发现有些迟到的跳蚤会插入到这一行的某个位置上,他感到非常生气,因为……他不会做了。
    请你帮一帮伏特吧。
    快捷版题意:带插入、修改的区间(k)小值在线查询。

    输入

    第一行一个正整数(n),表示原来有(n)只跳蚤排成一行做早操。
    第二行有(n)个用空格隔开的非负整数,从左至右代表每只跳蚤的弹跳力。
    第三行一个正整数(q),表示下面有多少个操作。
    下面一共q行,一共三种操作对原序列的操作:(假设此时一共(m)只跳蚤)

    1. (Q x y k): 询问从左至右第(x)只跳蚤到从左至右第(y)只跳蚤中,弹跳力第(k)小的跳蚤的弹跳力是多少。
      ((1 ≤ x ≤ y ≤ m, 1 ≤ k ≤ y - x + 1))
    2. (M x val): 将从左至右第x只跳蚤的弹跳力改为(val)
      ((1 <= x <= m))
    3. (I x val): 在从左至右第x只跳蚤的前面插入一只弹跳力为(val)的跳蚤。即插入后从左至右第(x)只跳蚤是刚插入的跳蚤。
      ((1 <= x <= m + 1))

    为了体现在线操作,设(lastans)为上一次查询的时候程序输出的结果,如果之前没有查询过,则(lastans = 0)(Q、M、I)操作的数全都要异或(lastans)才是真正输入。

    输出

    对于每个询问输出回答,每行一个回答。

    样例

    样例输入

    10
    10 5 8 28 0 19 2 31 1 22 
    30
    I 6 9
    M 1 11
    I 8 17
    M 1 31
    M 6 26
    Q 2 7 6
    I 23 30
    M 31 7
    I 22 27
    M 26 18
    Q 26 17 31
    I 5 2
    I 18 13
    Q 3 3 3
    I 27 19
    Q 23 23 30
    Q 5 13 5
    I 3 0
    M 15 27
    Q 0 28 13
    Q 3 29 11
    M 2 8
    Q 12 5 7
    I 30 19
    M 11 19
    Q 17 8 29
    M 29 4
    Q 3 0 12
    I 7 18
    M 29 27
    

    样例输出

    28
    2
    31
    0
    14
    15
    14
    27
    15
    14
    

    数据范围

    (n ≤ 35000)
    插入个数(≤ 35000),修改个数$ ≤ 70000(,查询个数) ≤ 70000$。
    $0 ≤ (任意时刻的权值)≤ 70000$。

    题解

    神题给跪了……
    怕不是可以当成一个模板背……

    不过如果不算低估替罪羊套线段树的巨量内存消耗而造成的(RE),可以算是(1A)……还是切入正题吧,不要东扯西扯了

    如果我们无视插入这个操作,就是一道傻逼的树状数组套主席树模板了。所以我们首先要解决插入的问题。

    如果可以离线,我们可以把所有会插入元素的位置先空出来(可以假装存在,赋一个(inf),保证不会被查到,然后把插入变成修改即可),做区间第(k)小就没有问题了。
    然而偏偏这道题要强制在线,我们就必须想办法动态维护线段树序列。鉴于可能会在任意一个位置插入,我们自然想到了平衡树维护。

    那么一个比较简陋的思路就出现了,我们用一棵平衡树维护序列,负责插入,然后套一个线段树,负责修改和询问。不过这个思路还很不完善——查询的时候怎么查?发现可以类似于树状数组套主席树的方法,平衡树的每个节点不只保存这个节点的信息,而是整棵子树的信息。

    对于每个查询,我们在平衡树上找到对应的节点(这些节点的平衡树信息合并后就是询问的区间,显然有些节点需要加上,有些需要减掉,所以这些点是加权的((1 or -1))),进行线段树上的二分答案,就可以查询出答案。

    但是我们会发现,平衡树没有那么容易套其他的树,因为平衡树的结构是会改变的,一旦旋转起来((AVL、SBT、Splay))或是合并和拆分((Treap)),线段树的形态也会发生改变,所以我们需要一种更稳定的结构——替罪羊树(都是套路)。

    然后就使劲儿写吧……反正(200+)行是稳了……

    (PS:)注意一下内存的问题,线段树必须写成动态开点,并且内存要记得回收,不然(RE)(MLE)稳稳的。还有,标题里的( imes)是套的意思,与(+)不一样。

    (Code:)

    #include <stack>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    #define N 200005
    #define M 20000005
    #define seg 0, 70000
    #define lim 0.77
    stack<int>pool;
    int n, m;
    struct SGT
    {
    	int lc, rc, sz;
    }S[M];
    struct SCT
    {
    	int root, cnt;
    	int lc[N], rc[N];
    	int rt[N], dt[N], sz[N];
    	int arr[N], num;
    	int A[2][N << 2], ct[2];
    	//线段树部分
    	int Newnode()//新建节点
    	{
    		int x = pool.top();
    		pool.pop();
    		return x;
    	}
    	void Recycle(int &x)//回收空间
    	{
    		pool.push(x);
    		S[x].lc = S[x].rc = S[x].sz = 0;
    		x = 0;
    	}
    	void Insert(int &q, int l, int r, int a, int v)//插入元素
    	{
    		if (!q)
    			q = Newnode();
    		S[q].sz += v;
    		if (l == r)
    			return;
    		int mid = (l + r) >> 1;
    		if (a <= mid)
    			Insert(S[q].lc, l, mid, a, v);
    		else
    			Insert(S[q].rc, mid + 1, r, a, v);
    	}
    	void Delete(int &q)//回收树
    	{
    		if (!q)
    			return;
    		Delete(S[q].lc);
    		Delete(S[q].rc);
    		Recycle(q);
    	}
    	//替罪羊树部分
    	void Pushup(int q)
    	{
    		sz[q] = sz[lc[q]] + sz[rc[q]] + 1;
    	}
    	void Getarray(int q)//拍扁
    	{
    		if (lc[q])
    			Getarray(lc[q]);
    		arr[++num] = q;
    		if (rc[q])
    			Getarray(rc[q]);
    	}
    	void Rebuild(int &q, int l, int r)//重建
    	{
    		if (l > r)
    		{
    			q = 0;
    			return;
    		}
    		int mid = (l + r) >> 1;
    		q = arr[mid];
    		Delete(rt[q]);
    		for (int i = l; i <= r; i++)
    			Insert(rt[q], seg, dt[arr[i]], 1);
    		Rebuild(lc[q], l, mid - 1);
    		Rebuild(rc[q], mid + 1, r);
    		Pushup(q);
    	}
    	void Maintain(int &q)//整个拍扁重建环节
    	{
    		num = 0;
    		Getarray(q);
    		Rebuild(q, 1, num);
    	}
    	void Insert(int &q, int k, int a)//插入在第k个元素前
    	{
    		if (!q)
    		{
    			q = ++cnt;
    			dt[q] = a;
    			sz[q] = 1;
    			Insert(rt[q], seg, a, 1);
    			return;
    		}
    		Insert(rt[q], seg, a, 1);
    		if (k <= sz[lc[q]] + 1)
    			Insert(lc[q], k, a);
    		else
    			Insert(rc[q], k - sz[lc[q]] - 1, a);
    		Pushup(q);
    	}
    	void Check(int &q, int k)//从上往下检查重建
    	{
    		if (sz[q] * lim < sz[lc[q]] || sz[q] * lim < sz[rc[q]])
    		{
    			Maintain(q);
    			return;
    		}
    		if (k == sz[lc[q]] + 1)
    			return;
    		if (k <= sz[lc[q]] + 1)
    			Check(lc[q], k);
    		else
    			Check(rc[q], k - sz[lc[q]] - 1);
    	}
    	int Modify(int &q, int k, int a)//修改第k个元素
    	{
    		if (k == sz[lc[q]] + 1)
    		{
    			int b = dt[q];
    			Insert(rt[q], seg, a, 1);
    			Insert(rt[q], seg, b, -1);
    			dt[q] = a;
    			return b;
    		}
    		int b;
    		if (k <= sz[lc[q]] + 1)
    			b = Modify(lc[q], k, a);
    		else
    			b = Modify(rc[q], k - sz[lc[q]] - 1, a);
    		Insert(rt[q], seg, a, 1);
    		Insert(rt[q], seg, b, -1);
    		return b;
    	}
    	void Getarray(int q, int k, int tp)//搞出数组
    	{
    		if (!q)
    			return;
    		if (k <= sz[lc[q]])
    		{
    			Getarray(lc[q], k, tp);
    			return;
    		}
    		A[tp][++ct[tp]] = rt[q];
    		if (rc[q])
    			A[!tp][++ct[!tp]] = rt[rc[q]];
    		Getarray(rc[q], k - sz[lc[q]] - 1, tp);
    	}
    	int Query(int l, int r, int k, bool tp)//二分答案
    	{
    		if (l == r)
    			return l;
    		int sum = 0;
    		for (int i = 1; i <= ct[0]; i++)
    			sum -= S[S[A[0][i]].lc].sz;
    		for (int i = 1; i <= ct[1]; i++)
    			sum += S[S[A[1][i]].lc].sz;
    		int mid = (l + r) >> 1;
    		if (sum < k)
    		{
    			for (int i = 1; i <= ct[0]; i++)
    				A[0][i] = S[A[0][i]].rc;
    			for (int i = 1; i <= ct[1]; i++)
    				A[1][i] = S[A[1][i]].rc;
    			return Query(mid + 1, r, k - sum, tp);
    		}
    		for (int i = 1; i <= ct[0]; i++)
    			A[0][i] = S[A[0][i]].lc;
    		for (int i = 1; i <= ct[1]; i++)
    			A[1][i] = S[A[1][i]].lc;
    		return Query(l, mid, k, tp);
    	}
    	int Query(int l, int r, int k)//查询[l, r]之间的第k小
    	{
    		ct[0] = ct[1] = 0;
    		Getarray(root, l - 1, 0);
    		Getarray(root, r, 1);
    		return Query(seg, k, 0);
    	}
    }T;
    int main()
    {
    	for (int i = 20000000; i >= 1; i--)
    		pool.push(i);
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++)
    	{
    		int a;
    		scanf("%d", &a);
    		T.Insert(T.root, i, a);
    		T.Check(T.root, i);
    	}
    	scanf("%d", &m);
    	int lastans = 0, x, y, k;
    	char opt[10];
    	for (int i = 1; i <= m; i++)
    	{
    		scanf("%s", opt);
    		if (opt[0] == 'I')
    		{
    			scanf("%d%d", &x, &y);
    			x ^= lastans;
    			y ^= lastans;
    			T.Insert(T.root, x, y);
    			T.Check(T.root, x);
    		}
    		if (opt[0] == 'M')
    		{
    			scanf("%d%d", &x, &y);
    			x ^= lastans;
    			y ^= lastans;
    			T.Modify(T.root, x, y);
    		}
    		if (opt[0] == 'Q')
    		{
    			scanf("%d%d%d", &x, &y, &k);
    			x ^= lastans;
    			y ^= lastans;
    			k ^= lastans;
    			printf("%d
    ", lastans = T.Query(x, y, k));
    		}
    	}
    }
    
  • 相关阅读:
    C# 字典 Dictionary 转 JSON 格式遍历
    jquery-easyui-tree异步树
    android 开发环境搭建
    解决android sdk 无法更新
    043——VUE中组件之使用.sync修饰符与computed计算属性实现购物车原理
    laravel中数据库迁移的使用:
    002PHP文件处理——文件处理 is_dir mkdir getcwd chdir rmdir
    001PHP文件处理——文件处理disk_total_space disk_free_space basename dirname file_exists filetype
    042——VUE中组件之子组件使用$on与$emit事件触发父组件实现购物车功能
    041——VUE中组件之pros数据的多种验证机制实例详解
  • 原文地址:https://www.cnblogs.com/ModestStarlight/p/8556504.html
Copyright © 2020-2023  润新知