• (三维偏序)陌上花开


    传送门

    CDQ分治是一个神奇的东西……说实话我觉得看它的讲义我是真的看不懂。感觉好像分治类的算法都这样,就是讲义讲不明白,得多做题才能慢慢理解。

    毕竟因为分治的题主要是不知道怎么实现……得具体题目具体分析。

    CDQ分治的精华我觉得是在于,他能通过“修改独立,允许离线”,转化为将序列分为两份,其中保证前一半的操作都早于后一半的询问,从而把动态修改动态查询的问题转换成了先进行一系列修改,最后再查询这么个问题,从而避免使用高级数据结构解决问题,简化代码量,优化时间复杂度。

    先来看这道题吧。

    这个题相当于修改询问二合一,每次先添加一个点之后查询一下有多少符合情况的点。

    首先我们先考虑一下二维的怎么做?排序+树状数组。

    那么三位的呢?模仿一下,把第一维排个序,那之后就只剩两维了。二维树状数组

    好吧我们显然只能用树状数组维护一维,那我们考虑一下怎么能使第二维也有序?

    那我们只能暂且牺牲一下第一维。这里我们就要用到上面的思路了。首先,我们所有的修改对答案的贡献是单独的,都是单独的一个点。因为我们要同时保证三维都小,而在我们把序列劈成两半以后,对于所有后面的询问,前面所有的操作必定x值都要小于他们。也就是转化成了,先进行一通修改,之后再来一通查询这样的问题。

    再直白一点说,就是现在x没什么限制作用,就相当于直接转化成了一个二维的问题,只要归并排序第二维或者直接sort第二维(这一点相当于我们处理二维的时候对第一维排序),第三维用树状数组维护统计答案即可。

    然后分治区间内部的答案统计递归下去就好了。注意答案是累加的。

    这样我们的时间复杂度是O(nlog2n),相对于高级数据结构常数较小,跑的还是比较快的。

    至于具体的实现,先对x排序,之后CDQ分治,在一个区间内,对y排序(归并或者sort都可以),之后开始枚举,对于后面每一个操作,把前面所有y小于它的操作都执行一次,之后统计一次答案,之后递归下去就好了。

    可能光听人说总是会很懵。看一下代码。

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<queue>
    #include<cstring>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    #define pr pair<int,int>
    #define mp make_pair
    #define fi first
    #define sc second
    #define lowbit(x) x & (-x)
    using namespace std;
    typedef long long ll;
    const int M = 200005;
    const int N = 10000005;
     
    int read()
    {
       int ans = 0,op = 1;
       char ch = getchar();
       while(ch < '0' || ch > '9')
       {
          if(ch == '-') op = -1;
          ch = getchar();
       }
       while(ch >='0' && ch <= '9')
       {
          ans *= 10;
          ans += ch - '0';
          ch = getchar();
       }
       return ans * op;
    }
    
    struct tree
    {
       int c[M];
       void add(int x,int k) {while(x <= M-2) c[x] += k,x += lowbit(x);}
       int ask(int x) {int p = 0;while(x) p += c[x],x -= lowbit(x);return p;}
    }T;
    
    struct node
    {
       int x,y,z,w,ans;
       bool operator != (const node &g) const
       {
          return x != g.x || y != g.y || z != g.z;
       }
       bool operator < (const node &g) const
       {
          if(x == g.x && y == g.y) return z < g.z;
          if(x == g.x) return y < g.y;
          return x < g.x;
       }
    }a[M],b[M];
    
    bool cmp(const node &f,const node &g)
    {
       if(f.y == g.y) return f.z < g.z;
       return f.y < g.y;
    }
    
    int n,k,tot,cur,sum[M];
    
    void CDQ(int l,int r)
    {
       if(l == r) return;
       int mid = (l + r) >> 1;
       CDQ(l,mid),CDQ(mid+1,r);//先分治下去
       sort(b+l,b+mid+1,cmp),sort(b+mid+1,b+r+1,cmp);//在里面按第二维排序排序
       int i = mid + 1,j = l;
       while(i <= r)
       {
          while(b[j].y <= b[i].y && j <= mid) T.add(b[j].z,b[j].w),j++;//把y在这次操作之前的全部添加
          b[i].ans += T.ask(b[i].z),i++;//计算这次答案
       }
       rep(k,l,j-1) T.add(b[k].z,-b[k].w);//把已经添加的操作还原
    }
    
    int main()
    {
       n = read(),k = read();
       rep(i,1,n) a[i].x = read(),a[i].y = read(),a[i].z = read();
       sort(a+1,a+1+n);
       rep(i,1,n)//这里是去重,因为有的点是重复的
       {
          cur++;
          if(a[i] != a[i+1]) b[++tot] = a[i],b[tot].w = cur,cur = 0;
       }
       CDQ(1,tot);//分治
       rep(i,1,tot) sum[b[i].ans + b[i].w - 1] += b[i].w;//因为这个题要求输出的很奇怪……所以这样计算答案
       rep(i,0,n-1) printf("%d
    ",sum[i]);
       return 0;
    }
  • 相关阅读:
    GITHUB随笔 15-5月 junit
    github心得
    (转)哪个瞬间让你突然觉得逛知乎真有用?
    0-1背包问题(the knapsack problem)
    对VSCode在安装了Dev-cpp的电脑上的配置
    非全日制考研资料(未完待续)
    CSS那些事读书笔记-2
    CSS那些事读书笔记-1
    前端面试准备-1
    工作日记-一个有趣的定时任务实现
  • 原文地址:https://www.cnblogs.com/captain1/p/10088936.html
Copyright © 2020-2023  润新知