P3810 【模板】三維偏序(陌上花開)(CDQ分治)

題目鏈接:https://www.luogu.com.cn/problem/P3810

 

題目大意:

  有n個元素,第i個元素有ai,bi,cia_i,b_i,c_i三個屬性,設f(i)f(i)表示滿足 ajaia_j \leq a_i​bjbib_j \leq b_icjcic_j \leq c_ijij\neq ijj的數量。

  對於 d[0,n)d∈[0,n),求f(i)=df(i) = d的數量。

 

題目思路:

  CDQ裸題,所謂CDQ就是一個處理多維偏序問題的一種通過分治解決的方法。首先先根據a排序,a相同按照b排序,b也相同按照c排序,都從小到大排好序。
  CDQ的核心思想是,把一段區間分成兩部分,左邊的a小於右邊區間的a,這樣有一個好處,就是隻有可能是左邊對右邊有貢獻,因爲左邊任意一個元素的a都比右邊的小。所以接下來就是快樂的分治。這裏有兩種做法,洛谷大部分大佬的題解都用了sort來保證左區間和右區間的b是各自有序的,但是我更加喜歡其中一個用完整歸併排序,來實現每個區間的b都是有序的,這樣能省掉一個log。在本題中,由於最開始已經對a,b,c排序,遞歸到只有兩個元素時,由於剛開始的操作,一定是左邊右邊的a和b都符合要求,歸併排序完成後,就變成了僅僅按照b來排序,a亂了,但這沒有關係!因爲我們只要求左邊對右邊有貢獻,我們能保證左邊區間的a都比右邊區間的a小即可!不用管其他花裏胡哨的。然後接下來就是歸併排序階段。比較左區間指針所指位置的b和右區間的b比較,如果是左邊小於等於右邊,剛纔說過了,a已經保證了左邊小於等於右邊,現在這個b小於等於當前右區間指針指向的位置,那麼右區間指針指向位置以及後面的b,就更不用說肯定更滿足了(兩個區間內的b都是有序的),所以現在就看c了,那麼就在樹狀數組中爲自己的c加上自己的數量(這裏一會兒解釋),也就是告訴右邊的,凡是現在冒出來的,比我這個c大的,都是能打敗我符合要求的。如果是右邊的話,就可以收割一波,看看自己的c能收穫多少個左邊的小夥子。最後別忘了收割乾淨,如果左區間沒走完也繼續。有人會說了,你這幹啥呢,這不是浪費嗎,明明左區間現在再更新樹狀數組已經沒用了,你還更新。這一部分其實是爲了歸併排序能更加完整,也就是這裏算完了,排序也完成了,能夠少兩個排序的複雜度。最後記得銷燬左區間的貢獻,現在當前區間算完了,留它無用,把左區間貢獻都刪了。最後把剛纔歸併排序得到的序列塞回去。現在解釋剛纔那個數量是咋回事。剛纔說了,只有左區間可以對右區間產生貢獻,那麼問題來了,對於完全相同的兩個元素,他們之間互相能產生貢獻,這個咋辦!所以,直接把他們合爲一體,一家人出一個代表,然後一個代表表明數量,說明這個代表有幾個人,算貢獻把一家人都算上。最後要求輸出的是對於i來說滿足要求的j的數量,對於每個數量輸出相應數量的i(好繞口!)這裏唯一需要注意的就是,對於一家人,每個家裏其他幾個人都會對自己有貢獻,比如5個人,那麼對於一個人來說,其他四個都是符合要求的j,所以需要+a[i].w-1
  坑點說明: 1、每個點的數量和對應的答案一定要一起放進結構體裏!!我這個憨憨特地給數量和答案另外開了數組,結果卡了2小時纔想起來,歸併排序每個點都在移動,如果在數組存不會跟着搬遷,就涼了。2、樹狀數組不止數組要開夠,裏面那個MAXN也得夠,犯了這錯也真是沒誰了。。。

以下是代碼:

#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define per(i,a,b) for(int i=a;i>=b;i--)
const int MAXN =2e5+5;
const int MAXM = 4e7+5;
const int MOD = 998244353;
int n,m,k,sum[MAXN<<2],cnt[MAXN];
struct node{
    int a,b,c,w,p;
}a[MAXN],b[MAXN];
bool cmp(node a,node b){
    if(a.a==b.a&&a.b==b.b)return a.c<b.c;
    if(a.a==b.a)return a.b<b.b;
    return a.a<b.a;
}
void add(int x,int y){
    for(;x<MAXN;x+=x&-x)sum[x]+=y;
}
int query(int x){
    int ans=0;
    for(;x;x-=x&-x)ans+=sum[x];
    return ans;
}
void cdq(int l,int r){
    if(l==r)return;
    int mid=(l+r)>>1;
    cdq(l,mid);cdq(mid+1,r);
    int i=l,j=mid+1,pos=l;
    while(i<=mid&&j<=r){
        if(a[i].b<=a[j].b)add(a[i].c,a[i].w),b[pos++]=a[i++];
        else a[j].p+=query(a[j].c),b[pos++]=a[j++];
    }
    while(i<=mid)add(a[i].c,a[i].w),b[pos++]=a[i++];
    while(j<=r)a[j].p+=query(a[j].c),b[pos++]=a[j++];
    rep(i,l,mid)add(a[i].c,-a[i].w);
    rep(i,l,r)a[i]=b[i];
}
int main()
{
    while(cin>>m>>k){
        memset(sum,0,sizeof(sum));
        memset(cnt,0,sizeof(cnt));
        rep(i,1,m)scanf("%d%d%d",&b[i].a,&b[i].b,&b[i].c);
        a[m+1].a=0;
        sort(b+1,b+m+1,cmp);
        int p=0;
        n=0;
        rep(i,1,m){
            p++;
            if(b[i].a!=b[i+1].a||b[i].b!=b[i+1].b||b[i].c!=b[i+1].c){
                n++;
                a[n].a=b[i].a,a[n].b=b[i].b,a[n].c=b[i].c;
                a[n].w=p,a[n].p=0;
                p=0;
            }
        }
        cdq(1,n);
        rep(i,1,n){
            cnt[a[i].p+a[i].w-1]+=a[i].w;
        }
        rep(i,0,m-1){
            printf("%d\n",cnt[i]);
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章