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

题目链接:https://www.luogu.com.cn/problem/P3810

 

题目大意:

  有n个元素,第i个元素有 a i , b i , c i a_i,b_i,c_i ai,bi,ci三个属性,设 f ( i ) f(i) f(i)表示满足 a j ≤ a i ​ a_j \leq a_i​ ajai b j ≤ b i b_j \leq b_i bjbi c j ≤ c i c_j \leq c_i cjci j ≠ i j\neq i j=i j j j的数量。

  对于 d ∈ [ 0 , n ) d∈[0,n) d[0,n),求 f ( i ) = d f(i) = d f(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]);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值