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

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

  • 相关阅读:
    在MVC3 中给HtmlHelper 添加扩展(Extension)来消除魔鬼代码
    NHibernate中使用IQueryOver时,如何添加或(OR)条件
    用@Html.EditorFor在MVC3中封装带表单(Form)提交的分页控件(通用代码)
    .NET MVC3使用CheckBox List(复选框列表)的简单方法
    Castle Windsor的MVC3的例子在最新版本(3.0Beta)上编译不过去的解决办法
    初级编程:编程巧妙注释【附图讲解】
    布同:如何循序渐进学习Python语言
    <转>:写给初学者的话《学习程序之路》
    初级编程:一层循环如何依次遍历二维数组【附代码】
    赛班S60的Python平台的源代码
  • 原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/6550744.html
Copyright © 2011-2022 走看看