P3810 【模板】三维偏序(陌上花开)

传送门

CDQ分治

先三关键字排序

然后把第二关键字归并排序

在合并子区间时用 第三关键字的权值树状树组 算出子区间的答案

为什么可以这样搞呢

首先第一维已经有序

所以只要考虑左边对右边的影响

把第二维归并时 左子区间的第一二关键字 全部小于或等于 右子区间的第一二关键字

所以也只要考虑左边对右边的影响

就用 第三关键字权值树状树组 搞一搞 二维偏序 就好了

二维偏序应该懂吧,实现和细节还是看代码吧

还要注意有重复的数

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
inline int read()
{
    int x=0; char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
    {
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return x;
}

const int N=4e5+7;
int n,m;
int a[N],b[N],c[N],p[N],cnt[N],ans[N],v[N];
//a,b,c为三个关键字,p存排序去重后的数据,这里没用结构体,可以省空间
//v是每个数重复的数量
inline bool cmp1(const int &x,const int &y){ return a[x]<a[y]|| (a[x]==a[y]&& (b[x]<b[y]|| (b[x]==b[y]&&c[x]<c[y]) ) ); }
//不用结构体排序的骚操作

int t[N];//数状数组
inline void add(int x,int v)
{
    while(x<=m)
    {
        t[x]+=v;
        x+=x&-x;
    }
}
inline int query(int x)
{
    int res=0;
    while(x)
    {
        res+=t[x];
        x-=x&-x;
    }
    return res;
}

int tmp[N];//归并时的临时数组
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,tot=0;
    while(i<=mid&&j<=r)
    {
        int x=p[i],y=p[j];
        if(b[x]<=b[y]) add(c[x],v[x]),tmp[++tot]=x,i++;
        else cnt[y]+=query(c[y]),tmp[++tot]=y,j++;
    }//核心,树状数组维护二维偏序

    while(j<=r) cnt[p[j]]+=query(c[p[j]]),tmp[++tot]=p[j],j++;
    while(i<=mid) tmp[++tot]=p[i],i++;
    //可能还有一些没处理完

    int mx=b[p[r]];
    for(i=l; i<=mid&&b[p[i]]<=mx ;i++) add(c[p[i]],-v[p[i]]);//用完要这样清空
    for(i=l;i<=r;i++) p[i]=tmp[i-l+1];//归并排序
}

int main()
{
    n=read(); m=read();
    for(int i=1;i<=n;i++)
        p[i]=i,a[i]=read(),b[i]=read(),c[i]=read();

    sort(p+1,p+n+1,cmp1);
    int tot=1;
    for(int i=2;i<=n;i++)
    {
        int x=p[i],y=p[tot];
        v[y]++;
        if(a[x]^a[y]||b[x]^b[y]||c[x]^c[y]) p[++tot]=x;
    }
    v[p[tot]]++;
    //去重并计算重复的数量

    cdq(1,tot);
    for(int i=1;i<=tot;i++) ans[cnt[p[i]]+v[p[i]]-1]+=v[p[i]];//注意有重复的数字
    for(int i=0;i<n;i++) printf("%d\n",ans[i]);
    return 0;
}

 

posted @ 2018-09-17 14:11  LLTYYC  阅读(196)  评论(0编辑  收藏  举报