HDU 6703(array 权值线段树)

HDU 6703(array 权值线段树)

来源:2019中国大学生程序设计竞赛(CCPC)
权值线段树的巧妙运用(对于蒟蒻的我来说
附上题目描述:
题目描述
You are given an array a1,a2,…,an(∀i∈[1,n],1≤ai≤n). Initially, each element of the array is unique.
Moreover, there are m instructions.
Each instruction is in one of the following two formats:
1.(1,pos),indicating to change the value of apos to apos+10,000,000;
2.(2,r,k),indicating to ask the minimum value which is not equal to any ai ( 1≤i≤r ) and **not less ** than k.
Please print all results of the instructions in format 2.
输入
The first line of the input contains an integer T(1≤T≤10), denoting the number of test cases.
In each test case, there are two integers n(1≤n≤100,000),m(1≤m≤100,000) in the first line, denoting the size of array a and the number of instructions.
In the second line, there are n distinct integers a1,a2,…,an (∀i∈[1,n],1≤ai≤n),denoting the array.
For the following m lines, each line is of format (1,t1) or (2,t2,t3).
The parameters of each instruction are generated by such way :
For instructions in format 1 , we defined pos=t1⊕LastAns . (It is promised that 1≤pos≤n) For instructions in format 2 , we defined r=t2⊕LastAns,k=t3⊕LastAns. (It is promised that 1≤r≤n,1≤k≤n )
(Note that ⊕ means the bitwise XOR operator. )
Before the first instruction of each test case, LastAns is equal to 0 .After each instruction in format 2, LastAns will be changed to the result of that instruction.
(∑n≤510,000,∑m≤510,000 )
输出
For each instruction in format 2, output the answer in one line.
样例输入
3
5 9
4 3 1 2 5
2 1 1
2 2 2
2 6 7
2 1 3
2 6 3
2 0 4
1 5
2 3 7
2 4 3
10 6
1 2 4 6 3 5 9 10 7 8
2 7 2
1 2
2 0 5
2 11 10
1 3
2 3 2
10 10
9 7 5 3 4 10 6 2 1 8
1 10
2 8 9
1 12
2 15 15
1 12
2 1 3
1 9
1 12
2 2 2
1 9
样例输出
1
5
2
2
5
6
1
6
7
3
11
10
11
4
8
11
提示
note:
After the generation procedure ,the instructions of the first test case are :
2 1 1, in format 2 and r=1 , k=1
2 3 3, in format 2 and r=3 , k=3
2 3 2, in format 2 and r=3 , k=2
2 3 1, in format 2 and r=3 , k=1
2 4 1, in format 2 and r=4 , k=1
2 5 1, in format 2 and r=5 , k=1
1 3 , in format 1 and pos=3
2 5 1, in format 2 and r=5 , k=1
2 5 2, in format 2 and r=5 , k=2
the instructions of the second test case are :
2 7 2, in format 2 and r=7 , k=2
1 5 , in format 1 and pos=5
2 7 2, in format 2 and r=7 , k=2
2 8 9, in format 2 and r=8 , k=9
1 8 , in format 1 and pos=8
2 8 9, in format 2 and r=8 , k=9
the instructions of the third test case are :
1 10 , in format 1 and pos=10
2 8 9 , in format 2 and r=8 , k=9
1 7 , in format 1 and pos=7
2 4 4 , in format 2 and r=4 , k=4
1 8 , in format 1 and pos=8
2 5 7 , in format 2 and r=5 , k=7
1 1 , in format 1 and pos=1
1 4 , in format 1 and pos=4
2 10 10, in format 2 and r=10 , k=10
1 2 , in format 1 and pos=2

题目大意
题目大意:给出一个n个元素的数组A,A中所有元素都是不重复的[1,n]。
有两种操作:
1.将pos位置的元素+1e7
2.查询不属于[1,r]中的最小的>=k的值。
强制在线。(想枯)
题解
因为数组中的值唯一,且在1到n的范围内,而询问的r和k也在1到n的范围内。 所以对于任意一个被操 作1修改过的值都不会成为询问的答案,而询问的结果也必然在k到n+1的范围内。 因为没有被修改过 值是唯一的,所以可以建立权值线段树,维护权值区间内的值所在下标的最大值。而询问则转化为不小 于k的值里面,下标超过r的最小权值是多少。 如何处理询问呢,一种较为暴力的解法是直接在线段树上 询问权值在k到n+1的范围内第一个下标超过r的权值是多少。但复杂度可能会被卡,需要减枝。 再加上 一个额外的判断就可以了,就是在递归查询完左子树内存不存在大于r的下标之后,如果不存在,则先 看一下右子树内的下标的最大值是否大于r。如果不大于r,则不必再进入右子树内查询,否则答案一定 在右子树内。在进左子树之前也利用同样的判断条件来判断是否有必要进入左子树,这样做可以保证单 次查询的复杂度是O(log n) 的。 而对于操作1,则等价于修改某个权值的下标为无穷大。操作复杂度也 是O(log n )的。 综上所述,得到了一个复杂度为O( m * log n )的在线算法,可以较快地通过此题。
个人版题解
权值线段树维护区间下标最大值 add操作正常添加 注意需要在添加个n+1这个元素,并把他的下标附上INF(很是巧妙,佩服)
查询操作就是寻找k到n+1中的下标大于r的最小值。如果不考虑时间可以从k到n+1单点询问直到大于r。可是时间不允许。
需要剪枝(还是佩服,体现出维护最大值的妙处) (下面的r用ask表示)
查询操作先查询左子树中pos的值是否大于ask如果左子树中的pos值大于ask答案一定在左子树中,直接查询左子树。不在左子树在右子树的条件是右子树的pos值大于ask,询问右子树。都不满足返回INF。
当递归到叶子节点的时候。也是分两种情况,1.如果当前pos值大于ask则满足,返回t[o].l即为答案。2.如果不满足返回INF。
现在仔细想想确定是这个道理 操作1对之后的操作不产生影响。操作2就是查询过程中多个剪枝。
赛后补题1遍A,唉…当时为啥想不到…菜是原罪

主席树好像也可以写,但我不会@_@

下面附上代码(纯自己手写 略丑)主要是注释内容

/*HDU 6703 array
权值线段树 在线处理
2019/08/27 12:00
*/
#pragma GCC optimize(3,"Ofast","inline")  	//G++
#pragma comment(linker, "/STACK:102400000,102400000")
#pragma GCC optimize(2)
#include
#include 
#define TEST freopen("in.txt","r",stdin);
#define mem(a,x) memset(a,x,sizeof(a))
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
typedef long long ll;
typedef unsigned long long ull; // %llu
const double PI = acos(-1.0);
const double eps = 1e-6;
const int mod=1e9+7;
const int INF = 1e6+5;
const int maxn = 1e5+5;
int n,q;
struct node
{
     
    int l,r,pos;    //建立权值线段树 维护区间下标最大值
} t[4*maxn];
int a[maxn];
void build(int l,int r,int o)   //一颗大树由此而生
{
     
    t[o].l=l;
    t[o].r=r;
    if(l==r)
    {
     
        return ;
    }
    int mid=(l+r)>>1;
    build(l,mid,o<<1);
    build(mid+1,r,o<<1|1);
}
void pushup(int o)          //自如其名 pushup
{
     
    t[o].pos=max(t[o<<1].pos,t[o<<1|1].pos);
}
void add(int o,int x,int pos)   //添加操作 维护区间下标最大值
{
     
    if(t[o].l==t[o].r&&t[o].l==x)
    {
     
        t[o].pos=pos;
        return ;
    }
    int mid=(t[o].l+t[o].r)>>1;
    if(x<=mid)
        add(o<<1,x,pos);
    else
        add(o<<1|1,x,pos);
    pushup(o);
}
int qurey(int o,int l,int r,int ask)
{
     
    if(t[o].l==t[o].r)      //到叶子节点判读答案是否成立
    {
     
        if(t[o].pos>ask)
            return t[o].l;
       else
           return INF;
    }
    if(t[o].l==l&&t[o].r==r)        //在当期节点区间时 需要根据左右子树的pos值判读下一步往哪边走(剪枝)
    {
     
        if(t[o<<1].pos>ask)
        {
     
            return qurey(o<<1,l,(l+r)>>1,ask);
        }
        else if(t[o<<1|1].pos>ask)
        {
     
            return qurey(o<<1|1,(l+r)>>1|1,r,ask);
        }
       else
           return INF;
    }
    int mid=(t[o].l+t[o].r)>>1;         //熟悉的操作
    if(r<=mid)
    {
     
        return qurey(o<<1,l,r,ask);     //锁定区间
    }
    else if(l>mid)
    {
     
        return qurey(o<<1|1,l,r,ask);   //还是锁定区间
    }
    else
    {
     
        return min(qurey(o<<1,l,mid,ask),qurey(o<<1|1,mid+1,r,ask));        //锁定区间 答案即可能在左子树也可能在右子树 所以取左右子树中最小的值(有可能左子树返回INF)
    }
}
void init()     //输入
{
     

    scanf("%d%d",&n,&q);
    build(1,1+n,1);
    for(int i=1; i<=n; i++)
    {
     
        scanf("%d",&a[i]);
        add(1,a[i],i);
    }
    add(1,n+1,INF);
}
void solve()        //解决
{
     
    int op,last=0;      //在线处理
    while(q--)
    {
     
        scanf("%d",&op);
        if(op==1)
        {
     
            int pos;
            scanf("%d",&pos);
            pos^=last;
            add(1,a[pos],INF);
        }
        else
        {
     
            int r,k;
            scanf("%d%d",&r,&k);
            r^=last,k^=last;
            last=qurey(1,k,n+1,r);
            printf("%d\n",last);
        }
    }
}
int main()
{
     
//    TEST
    int T;
    scanf("%d",&T);
    while(T--)
    {
     
        mem(t,0);
        init();
        solve();
    }
}

你可能感兴趣的:(权值线段树,数据结构,权值线段树)