CDQ分治/三维偏序 详解

先简述一下题意

现在有一个长度为n的序列,每一个元素有三个属性,求对于每一个元素i,满足aj​≤ai​,bj​≤bi​,cj​≤ci​的j的个数

我们先来从特殊化入手

一维偏序

由于只有一个限制,所以我们考虑把每一个元素从小到大排序,对于元素i,答案为i−1

二维偏序

即一本通提高篇树状数组数星星

而那题的正解是双关键字排序后用树状数组维护

三维偏序

首先我们来找出这些元素的中点mid,然后我们会发现对于任意的元素对(i,j),会划分为如下的三种情况

  • i≤mid,j≤mid

  • i≥mid,j≥mid

  • i≥mid,j≤mid

对于前两种情况,我们考虑用归并排序的方法递归处理

对于第三种情况

我们首先排序满足了第一层限制,即aj​≤ai​

然后我们用双指针找出第一个bj​≥bi​的,这样可以保证j−1之前的数均满足前两层的限制

对于最后的一层限制,我们考虑用二维偏序的方法使用树状数组

以上就是CDQ分治的思想

这样我们就愉悦地解决这道题了

然后贴一下代码

#include <bits/stdc++.h>
using namespace std;
const int N=100010,M=N*2;
int n,m;
struct node//用结构体存储每一个元素
{
    int a,b,c,s;
    int res;
    bool operator<(const node& t)const
    {
        if(a!=t.a)return a<t.a;
        if(b!=t.b)return b<t.b;
        return c<t.c;
    }
    bool operator==(const node& t)const
    {
        return a==t.a&&b==t.b&&c==t.c;
    }
}q[N],w[N];
int t[M],ans[N];
int lowbit(int x)//树状数组板子
{
    return x&-x;
}
void add(int pos,int x)
{
    for(int i=pos;i<M;i+=lowbit(i))
        t[i]+=x;
    return;
}
int query(int pos)
{
    int res=0;
    for(int i=pos;i>=1;i-=lowbit(i))
        res+=t[i];
    return res;
}
void merge(int l,int r)//归并排序
{
    if(l>=r)return;
    int mid=l+r>>1;
    merge(l,mid);merge(mid+1,r);
    int i=l,j=mid+1,k=0;
    while(i<=mid&&j<=r)
        if(q[i].b<=q[j].b)add(q[i].c,q[i].s),w[k++]=q[i++];
        else q[j].res+=query(q[j].c),w[k++]=q[j++];
    while(i<=mid)add(q[i].c,q[i].s),w[k++]=q[i++];
    while(j<=r)q[j].res+=query(q[j].c),w[k++]=q[j++];
    for(i=l;i<=mid;i++)add(q[i].c,-q[i].s);
    for(i=l,j=0;j<k;i++,j++)q[i]=w[j];
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        q[i]={a,b,c,1};
    }
    sort(q,q+n);
    int k=1;//这里是解决了元素的三个属性都重复的情况,去一下重
    for(int i=1;i<n;i++)
        if(q[i]==q[k-1])q[k-1].s++;
        else q[k++]=q[i];
    merge(0,k-1);
    for(int i=0;i<k;i++)
        ans[q[i].res+q[i].s-1]+=q[i].s;//更新答案
    for(int i=0;i<n;i++)
        printf("%d\n",ans[i]);
    return 0;
}

然后我们再来看一道应用题

题目链接P3755

先简述一下题意

就是在坐标系上有n个点,框出m个矩阵求出该矩阵内的点权和

首先,排个序,然后离线。之后我们可以先求出从(0,0)(0,0)到(x,y)的点权和,可以用三维偏序来做。这里用三维偏序的原因是因为我们询问的点是不会对答案产生任何贡献的,所以,这里需要加上一层限制,把此题变成三维偏序

然后求一个子矩阵的和,不难想到,可以用二维前缀和来求出44个点到(0,0)(0,0)的点权和,再进行计算

到这里这题其实已经分析的差不多了,然后再说一下这里不用树状数组的原因。

不用树状数组是因为我们最后的一层限制只有两种状态,已经给出的点和询问的点,所以不需要使用树状数组

然后贴一下代码,跟上一题差不多

#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int N=1000010;
int n,m;
int ans[N];
struct node
{
    int x,y,z,p,id,sign;//x,y,z表示三层限制,p表示点权,id表示询问的编号,sign表示正负号
    int sum;
    bool operator <(const node& t)const
    {
        if(x!=t.x)return x<t.x;
        if(y!=t.y)return y<t.y;
        return z<t.z;
    }
}q[N],w[N];
void merge(int l,int r)//归并排序
{
    if(l>=r)return;
    int mid=l+r>>1;
    merge(l,mid);merge(mid+1,r);
    int i=l,j=mid+1,k=0;
    int sum=0;//记录符合条件的点权和
    while(i<=mid&&j<=r)
        if(q[i].y<=q[j].y)sum+=!q[i].z*q[i].p,w[k++]=q[i++];
        else q[j].sum+=sum,w[k++]=q[j++];
    while(i<=mid)sum+=!q[i].z*q[i].p,w[k++]=q[i++];
    while(j<=r)q[j].sum+=sum,w[k++]=q[j++];
    for(int i=l,j=0;j<k;i++,j++)q[i]=w[j];
}
signed main()
{
    cin>>n>>m;
    for(int i=0;i<n;i++)
    {
        int x,y,p;
        scanf("%lld%lld%lld",&x,&y,&p);
        q[i]={x,y,0,p};
    }
    int k=n;
    for(int i=1;i<=m;i++)
    {
        int x1,y1,x2,y2;
        scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
        q[k++]={x2,y2,1,0,i,1};//二维前缀和,把子矩阵拆成4个点
        q[k++]={x1-1,y2,1,0,i,-1};
        q[k++]={x2,y1-1,1,0,i,-1};
        q[k++]={x1-1,y1-1,1,0,i,1};
    }
    sort(q,q+k);
    merge(0,k-1);
    for(int i=0;i<k;i++)//更新答案
        if(q[i].z)
            ans[q[i].id]+=q[i].sum*q[i].sign;
    for(int i=1;i<=m;i++)
        printf("%lld\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值