(三维偏序)陌上花开

传送门

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;
}
原文地址:https://www.cnblogs.com/captain1/p/10088936.html