zoukankan      html  css  js  c++  java
  • [bzoj3110][Zjoi2013]K大数查询

    好题!

    法1:看到题目,首先想到的便是树套树。按照一般想法,第一维是区间,第二维权值,不好想(至少我不会。。。据说有人这么干,orz)

    如果反过来,做法就十分清晰了。对于区间[l,r],将权值在此之内的修改建立一棵普通线段树。这样对于一个询问,就可以类似二分答案,首先看权值在[1,mid]中有几个在询问的区间中,如果<排名,就往右,否则往左。

    /**************************************************************
        Problem: 3110
        User: lazycal
        Language: C++
        Result: Accepted
        Time:8436 ms
        Memory:201624 kb
    ****************************************************************/
     
    #include <cstdio>
    const int N = 50000+9,M = N * 16 * 16;
    int root[N * 4],n,m,sum[M],lc[M],rc[M],lazy[M],c,L,R,cnt;
    inline int min(const int &a,const int &b){return a>b?b:a;}
    inline int max(const int &a,const int &b){return a>b?a:b;}
    int count(const int idx,const int l,const int r)
    {
        if (L <= l && r <= R) return sum[idx];
        int t1 = 0,t2 = 0,mid = (l+r)/2;
        if (L <= mid) t1 = count(lc[idx],l,mid);
        if (mid < R)  t2 = count(rc[idx],mid + 1,r);
        return (t1 + t2) + (min(r,R)-max(L,l) + 1) * lazy[idx];
    }
    int query()
    {
        int l = 1, r = n, now = 1;
        for (;l != r;) {
            int mid = (l + r)/2, tmp;
            if ((tmp = count(root[now*2],1,n)) >= c) r = mid,now *= 2;
            else l = mid + 1,now = now*2 +1,c -= tmp;
        }
        return l;
    }
    void modify(int &idx,const int l,const int r)
    {
        if (!idx) idx = ++cnt;
        if (L <= l && r <= R) return (void)(sum[idx] += r - l + 1, ++lazy[idx]);
        int mid = (l + r)/2;
        if (L <= mid) modify(lc[idx],l,mid);
        if (mid < R) modify(rc[idx],mid + 1,r);
        sum[idx] = sum[lc[idx]] + sum[rc[idx]] + lazy[idx] * (r - l + 1);
    }
    void update()
    {
        int l = 1, r = n, now = 1;
        for (;l != r;) {
            int mid = (l + r)/2;
            modify(root[now],1,n);
            if (mid < c) l = mid + 1,now = now*2 + 1;
            else r = mid,now *= 2;
        }
        modify(root[now],1,n);
    }
    int main()
    {
        #ifndef ONLINE_JUDGE
        freopen("3110.in","r",stdin);
        freopen("3110.out","w",stdout);
        #endif
        scanf("%d%d",&n,&m);
        while (m--) {
            int flag;
            scanf("%d%d%d%d",&flag,&L,&R,&c);
            if (flag == 1) c = n - c + 1,update();
            else printf("%d
    ",n-query()+1);
        }
    }
    

      

    法2:分治(orz)

    同样是二分,令solve(l,r)代表解决ans = l...r的询问 (什么?我怎么知道ans在哪个区间?别急……),很明显,solve(1,n)即为所求。

    对于solve(l,r),任务就是将询问分组。分组等价于判断ans 与 (l+r)/2 的大小关系。也就是在[l,(l+r)/2]的数够不够k个。

    处理这个问题,可以将要插入的数中<(l+r)/2的数插入到数据结构中,比如这个操作区间是L[i],R[i],那就把L[i]...R[i]中每个数+1。这样判断ans 与 (l+r)/2 的大小关系时候直接在数据结构中查询,就可以得到在[l,(l+r)/2]的且在询问区间的数的个数。然后完成分组,solve(l,(l+r)/2) solve((l+r)/2+1,r)

    对于数据结构的选择,个人推荐使用树状数组。不过得懂得树状数组如何改段求段。下面附上树状数组改段---->改点的推导:

    delta[i] = A[i] - A[i - 1] {1 <= i <= n}
    Add(s,t,c):
      //A[s] += c;
      delta[s] = A[s] + c - A[s - 1] = delta[s] + c;
      delta[s] += c
      delta[s]*s += s*c
      //A[t] += c;
      delta[t + 1] = A[t + 1] - (A[t] + c) = delta[t + 1] - c;
      delta[t + 1] -= c
      delta[t + 1]*(t + 1) -= c*(t + 1)
    Sum(s,t,c):
      A[s] + ... + A[t]
      A[s] = A[s - 1] + delta[s] = A[s - 2] + delta[s] + delta[s - 1] = ... = delta[1] + ... + delta[s]
      A[s] + ... + A[t] = (delta[1] + ... + delta[s]) * (t - s + 1) + (t - s - i + 1) * delta[s + i] {1 <= i <= t - s}
      = (delta[1] + ... + delta[s]) * (t - s + 1) + (t + 1 - (s + i)) * delta[s + i] {1 <= i <= t - s}

    /**************************************************************
        Problem: 3110
        User: lazycal
        Language: C++
        Result: Accepted
        Time:1560 ms
        Memory:3148 kb
    ****************************************************************/
     
    #include <cstdio>
    const int N = 50000 + 9;
    int a1[2][N],a2[2][N],n,m,ans[N],a[N],b[N],c[N],tmp1[N],tmp2[N],times,t[N],flag[N];
    int count(int (&data)[2][N],int x)
    {
        int res = 0;
        for (;x;x -= x & -x)
            if (data[0][x] == times) res += data[1][x];
        return res;
    }
    void add(int (&data)[2][N],int x,const int d)
    {
        for (;x <= n;x += x & -x)
            if (data[0][x] == times) data[1][x] += d;
            else data[0][x] = times, data[1][x] = d;
    }
    int count(const int s,const int t)
    {
        return count(a1,s) * (t - s + 1) + (t + 1) * (count(a1,t) - count(a1,s)) - (count(a2,t) - count(a2,s));
    }
    void add(const int s,const int t,const int c)
    {
        add(a1,s,c); add(a2,s,s*c);
        add(a1,t + 1,-c); add(a2,t + 1, -c*(t + 1));
    }
    void solve(const int l1,const int r1,const int l,const int r)
    {
        if (l1 > r1) return ;
        if (l == r) {
            for (int i = l1; i <= r1; ++i)
                if (flag[t[i]] == 2) ans[t[i]] = l;
            return ;
        }
        int tmp; ++times;
        tmp1[0] = tmp2[0] = 0;
        const int mid = (l + r)/2;
        for (int i = l1; i <= r1; ++i)
            if (flag[t[i]] == 1) 
                if (c[t[i]] <= mid) {
                    tmp1[++tmp1[0]] = t[i];
                    add(a[t[i]],b[t[i]],1);
                }else tmp2[++tmp2[0]] = t[i];
            else
                if ((tmp = count(a[t[i]],b[t[i]])) < c[t[i]]) {
                    c[t[i]] -= tmp;
                    tmp2[++tmp2[0]] = t[i];
                }else tmp1[++tmp1[0]] = t[i];
         
        int mid1 = tmp1[0] + l1 - 1;
        for (int i = l1; i <= mid1; ++i) t[i] = tmp1[i - l1 + 1];
        for (int i = mid1 + 1; i <= r1; ++i) t[i] = tmp2[i - mid1];
         
        solve(l1, mid1, l, mid);
        solve(mid1 + 1, r1, mid + 1, r);
    }
    int main()
    {
        #ifndef ONLINE_JUDGE
        freopen("3110_2.in","r",stdin);
        freopen("3110_2.out","w",stdout);
        #endif
        scanf("%d%d",&n,&m);
        for (int i = 1; i <= m; ++i) {
            scanf("%d%d%d%d",flag+i,a+i,b+i,c+i);
            if (flag[i] == 1) c[i] = n - c[i] + 1;
            t[i] = i;
        }
        solve(1,n,1,n);
        for (int i = 1; i <= m; ++i)
            if (flag[i] == 2) printf("%d
    ",n-ans[i]+1);
    }
    

      

  • 相关阅读:
    硬件IC汇总
    stm8s103调试注意点
    感悟短句
    USB接口
    液晶屏驱动注意
    四数之和
    所有奇数长度子数组的和
    秋叶收藏集
    删除中间节点
    组合总和
  • 原文地址:https://www.cnblogs.com/lazycal/p/3239304.html
Copyright © 2011-2022 走看看