1. 程式人生 > 其它 >萬字長文·三維偏序/cdq分治

萬字長文·三維偏序/cdq分治

萬字長文·三維偏序/cdq分治

cdq分治

靜態區間問題

cdq分治是一種解決區間統計問題的常用方法

點分治相當於吧cdq分治挪到樹上

cdq分治即把一個序列分為兩部分 區間統計的量分為跨中點的 左邊的 右邊的

(一下所說統計均指在區間中點)

先遞迴處理左邊的和右邊的

然後再尋找某種\(O(n)\)或較優方式統計跨中點的

熟知歸併排序求逆序對

這就是使用cdq分治解決二維偏序的例子

離線的帶修資料結構問題

cdq 分治被稱為 動態問題轉化為靜態問題的工具

發現離線的資料結構離線下來的操作是一個序列

每個修改操作會對它後面的詢問操作產生影響

使用cdq分治來完成這個問題

先遞迴處理[l,mid],[mid+1,r]

然後加入[l,mid]中操作對[mid+1,r]中詢問的影響 並得出答案

正確性:顯然所有修改按時間順序排序

一個例子

矩形加矩形求和¶
這裡的矩形加矩形求和就是字面意思上的矩形加矩形求和,
讓你維護一個二維平面,然後支援在一個矩形區域內加一個數字,
每次詢問一個矩形區域的和

這個問題的不帶修就是經典的掃描線問題

加入修改後只需按照cdq套路完成即可qwq

三維偏序

顯然三維偏序問題是二維偏序的擴充套件

二維偏序有兩種寫法:歸併排序(cdq分治)和樹狀陣列

考慮結合二者

先對第一維排序

然後顯然[l,mid]和[mid+1,r]只會在兩者之間產生偏序

我們可以使用雙指標掃左區間和右區間 同二維偏序一樣

將左區間第二維小於右區間的第三維插入樹狀陣列 將對應於左區間的右區間首個b<左區間點的點的c值在樹狀陣列中查詢字首和

正確性與歸併排序同理

細節:對於相同點 顯然二者可以互相貢獻答案 而該演算法顯然只能計算一次 所以要去重再把重值算回

程式碼

#include<bits/stdc++.h>
using namespace std;
#define N 200005
struct node
{
  long long a,b,c,cnt,ans=0;
}q1[N],q2[N];
long long n,kk,asn[N];
bool cmp(node x,node y)
{
  if(x.a==y.a)
  {
    if(x.b==y.b)
      return x.c<y.c;
    else 
      return x.b<y.b;
  }
  else return x.a<y.a;
}
bool cmp2(node x,node y)
{
  if(x.b==y.b)
      return x.c<y.c;
  else 
    return x.b<y.b;
}
struct tyz
{
  long long tr[N];
  void change(long long x,long long k)
  {
    for(;x<=kk;x+=(x&(-x)))
      tr[x]+=k;
  }
  long long ask(long long x)
  {
    long long ret=0;
    for(;x>0;x-=(x&(-x)))
      ret+=tr[x];
    return ret;
  }
  void clear()
  {
    for(long long i=1;i<=kk;i++) tr[i]=0;
  }
}qt;
void cdq(long long l,long long r)
{
  if(l==r) return;
  long long mid=(l+r)>>1;
  cdq(l,mid),cdq(mid+1,r);
  long long ls=l,rs=mid+1;long long i=l;
  for(;i<=r&&ls<=mid&&rs<=r;)
  {
    if(q2[ls].b<=q2[rs].b) 
      qt.change(q2[ls].c,q2[ls].cnt),
      q1[i].a=q2[ls].a,
      q1[i].b=q2[ls].b,
      q1[i].c=q2[ls].c,
      q1[i].ans=q2[ls].ans,
      q1[i].cnt=q2[ls].cnt,
      i++,
      ls++; 
    else
      q2[rs].ans+=qt.ask(q2[rs].c),
      q1[i].a=q2[rs].a,
      q1[i].b=q2[rs].b,
      q1[i].c=q2[rs].c,
      q1[i].ans=q2[rs].ans,
      q1[i].cnt=q2[rs].cnt,
      i++,
      rs++; 
  }
  while(rs<=r)
  {
    q2[i].ans+=qt.ask(q2[rs].c),
    q1[i].a=q2[rs].a,
    q1[i].b=q2[rs].b,
    q1[i].c=q2[rs].c,
    q1[i].ans=q2[rs].ans,
    q1[i].cnt=q2[rs].cnt,
    i++;
    rs++; 
  }
  for(int i=l;i<ls;i++)
    qt.change(q2[i].c,-q2[i].cnt);
  while(ls<=mid)
  {
    q1[i].a=q2[ls].a,
    q1[i].b=q2[ls].b,
    q1[i].c=q2[ls].c,
    q1[i].ans=q2[ls].ans,
    q1[i].cnt=q2[ls].cnt,
    i++;
    ls++; 
  }
  for(long long kkk=l;kkk<=r;kkk++)
  {
    q2[kkk].a=q1[kkk].a,
    q2[kkk].b=q1[kkk].b,
    q2[kkk].c=q1[kkk].c,
    q2[kkk].ans=q1[kkk].ans,
    q2[kkk].cnt=q1[kkk].cnt;
  }
  
}
int main()
{
  scanf("%d%d",&n,&kk);
  for(long long i=1;i<=n;i++)
    scanf("%d%d%d",&q1[i].a,&q1[i].b,&q1[i].c);
  sort(q1+1,q1+1+n,cmp);
  long long m=0,top=0;
  for(long long i=1;i<=n;i++)
  {
    top++;
    if(q1[i].a!=q1[i+1].a||q1[i].b!=q1[i+1].b||q1[i].c!=q1[i+1].c)
    {
      m++;
      q2[m].a=q1[i].a;
      q2[m].b=q1[i].b;
      q2[m].c=q1[i].c;
      q2[m].cnt=top;
      top=0;
    }
  }
  cdq(1,m);
  for(long long i=1;i<=m;i++)
    asn[q2[i].ans+q2[i].cnt-1]+=q2[i].cnt;
  for(long long i=0;i<n;i++)
    cout<<asn[i]<<endl;
  return 0;
}