zoukankan      html  css  js  c++  java
  • bzoj 1901: Zju2112 Dynamic Rankings

    1901: Zju2112 Dynamic Rankings

    Time Limit: 10 Sec  Memory Limit: 128 MB

    Description

    给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。对于每一个询问指令,你必须输出正确的回答。 第一行有两个正整数n(1≤n≤10000),m(1≤m≤10000)。分别表示序列的长度和指令的个数。第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。接下来的m行描述每条指令,每行的格式是下面两种格式中的一种。 Q i j k 或者 C i t Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t。

    Input

    对于每一次询问,你都需要输出他的答案,每一个输出占单独的一行。

    Output

    Sample Input

    5 3
    3 2 1 4 7
    Q 1 4 3
    C 2 6
    Q 2 5 3

    Sample Output

    3
    6

    HINT

    20%的数据中,m,n≤100; 40%的数据中,m,n≤1000; 100%的数据中,m,n≤10000。

    上图摘自 许昊然《浅谈数据结构题的几个非经典解法》http://www.docin.com/p-950607443.html

    个人理解:

    假设有一个操作,可以直接二分一个判定标准,这个标准仅适用于这一个操作

    那么现在有一堆操作,也是直接二分判定标准

    而二分出来的标准,如果只有一个操作,非A即B

    对于一堆操作,每个操作对于判定标准也是非A即B

    如果只有一个操作,那就可以直接舍去不合要求的那个

    但一堆操作,会出现,操作1符合A,操作2符合B

    所以,我们就根据这个判断标准,将操作序列划分为2组

    注意:划分依据是操作对象相对于判断标准的关系

    对于划分出来的2组,同样的方法可以继续二分

    直至将判断标准划分至最底层

    所以整体二分每次操作序列是原序列截取的一段,它保证了时间复杂度

    一篇很好的CDQ分治、整体二分总结:http://blog.csdn.net/hbhcy98/article/details/50642773

    本题:整体二分+树状数组

    #include<cstdio>
    using namespace std;
    #define N 10001
    int n,m,tot,t;
    int ans[N*2],a[N],k[N*3];
    int c[N];
    struct node
    {
        int posx,posy,key,kind,bl,cur;
    }q[N*3],tmp1[N*3],tmp2[N*3];
    int lowbit(int x)
    {
        return x&(-x);
    }
    void add(int x,int y)
    {
        while(x<=n)
        {
            c[x]+=y;
            x+=lowbit(x);
        }
    }
    int sum(int x)
    {
        int b=0;
        while(x)
        {
            b+=c[x];
            x-=lowbit(x);
        }
        return b;
    }
    void solve(int head,int tail,int l,int r)
    //当前二分到的答案区间[l,r] 
    //[head,tail]是操作序列,包括对答案区间[l,r]有影响的修改操作  和  答案在[l,r]内的查询操作
    //这里二分区间是[l,r],[head,tail]标记的是根据而分出的判断标准,划分出的区间范围 
    {
        if(head>tail) return;
        if(l==r)
        {
            for(int i=head;i<=tail;i++) 
             if(q[i].kind==3) ans[q[i].bl]=l;
            return;
        }
        int mid=l+r>>1;//设定判定标准
    //在本题中,判定标准=mid,那么判定标准对应区间为[1,mid],即实际的判定标准是区间[1,mid],这一点相当重要 
    //统计符合标准的修改对各个操作的贡献 
        for(int i=head;i<=tail;i++)
        {
            if(q[i].kind==1&&q[i].key<=mid) add(q[i].posx,-1);
            else if(q[i].kind==2&&q[i].key<=mid) add(q[i].posx,1);
            else if(q[i].kind==3) k[i]=sum(q[i].posy)-sum(q[i].posx-1);
    //这里用k数组记录的数据相当于前缀和 
        }
    //整体二分每次对一段新的序列操作,这就需要清空一些数据
    //本题要清空树状数组,据说下面这4行比memeset快 
        for(int i=head;i<=tail;i++) 
        {
            if(q[i].kind==1&&q[i].key<=mid) add(q[i].posx,1);
            else if(q[i].kind==2&&q[i].key<=mid) add(q[i].posx,-1);
        }
        int ll=0,rr=0;
        for(int i=head;i<=tail;i++)
        {
            if(q[i].kind==3)//查询操作 
            {
    //如果累计贡献+当前贡献>=目标贡献,即当前判定标准过于宽松,那么就要缩小累计贡献范围,将它归入答案区间[l,mid]
    //此时不更新累计贡献,保证当前贡献仍是[1,l-1]里的 
                if(q[i].cur+k[i]>=q[i].key) tmp1[++ll]=q[i];
    //累计贡献+当前贡献<目标贡献,即当前判定标准不能满足要求,就要夸大范围,把它归入答案区间[mid+1,r]
    //更新累计贡献,使当前贡献是[1,mid]里的 
                else 
                {
                    q[i].cur+=k[i];
                    tmp2[++rr]=q[i];
                }
            }
            else//修改操作 
            {
    //因为二分的是答案(判定标准)
    //所以直接根据修改后的值与判定标准的关系划分即可
    //注意是修改后的值,即把位置h改为g,判断的是g,而不是位置h 
    //为什么?
    
    //如果g满足判定标准,那么他就已经统计了一次贡献,它对于答案区间[mid+1,r]没有意义了,
    //想想判定标准实际是[1,mid],他一定会对[1,[mid+1......r]]产生贡献,这个贡献我们已经累计更新了cur 
                if(q[i].key<=mid) tmp1[++ll]=q[i];
    //g不符合标准,意思是如果放宽标准可能会有贡献,而且这个贡献还没有统计过 
                else tmp2[++rr]=q[i];
            }
        }
        for(int i=1;i<=ll;i++) q[head+i-1]=tmp1[i];
        for(int i=1;i<=rr;i++) q[head+ll+i-1]=tmp2[i];
    //都是开区间,所以要-1 
        solve(head,head+ll-1,l,mid);solve(head+ll,tail,mid+1,r);
    //看完这儿,我们可以对tmp1,tmp2感性的认知 
    //tmp1 满足要求或过于满足,要进一步限制 
    //tmp2 不满足要求,放宽限制可能会满足要求 
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        int x,y,z;
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&x);
            q[++tot]=(node){i,0,x,2};
            a[i]=x;
        }
        char ch[2];
        for(int i=1;i<=m;i++)
        {
            scanf("%s",ch);
            if(ch[0]=='Q') 
            {
                scanf("%d%d%d",&x,&y,&z);
                q[++tot]=(node){x,y,z,3,++t,0};
            }
            else 
            {
                scanf("%d%d",&x,&z);
                q[++tot]=(node){x,0,a[x],1,0,0};
                q[++tot]=(node){x,0,z,2,0,0};
    //修改操作,先将原来的信息去除,再添加新的信息 
                a[x]=z;
            }
        }
        solve(1,tot,0,1e9);
    //这里是0,不是1,因为二分答案,题目修改值可能为0 
        for(int i=1;i<=t;i++) printf("%d
    ",ans[i]);
    }

    刚开始的时候,为了减少潜在的时间消耗,把mid,ll,rr3个变量放到全局变量里了

    然后WAWAWAWA

    递归局部变量可以保持本证递归回溯是变量还是原来那个值,全局变量改了就是改了

  • 相关阅读:
    700. Search in a Binary Search Tree
    100. Same Tree
    543. Diameter of Binary Tree
    257. Binary Tree Paths
    572. Subtree of Another Tree
    226. Invert Binary Tree
    104. Maximum Depth of Binary Tree
    1、解决sublime打开文档,出现中文乱码问题
    移植seetafaceengine-master、opencv到ARM板
    ubuntu16.04-交叉编译-SeetaFaceEngine-master
  • 原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/6550744.html
Copyright © 2011-2022 走看看