洛谷P3810(三维偏序,CDQ分治果题)

CDQ分治算法解析
本文介绍了一道关于CDQ分治算法的题目,详细解释了如何将动态问题转化为静态问题,通过排序和BIT实现三维逆序对的计算。特别讨论了稳定性排序的重要性及其对正确答案的影响。

题面
最近米娜都在学cdq分治,看到题都直接想CDQ,还讲得头头是道,所以本蒟蒻也水一水。找到bzoj的一题果题,居然还是权限的,但我还是找到了本题。

个人理解,有一个修改和查询的序列,CDQ就是可以离线的条件下,用logN的复杂度,把动态的问题转化为静态的问题,即把边修改边查询改为先修改再查询。

主要思想大概是这样的,有一个询问和查询的序列区间[L,R],二分一个mid,先处理子问题[l,mid],再用与(R-L)相关的复杂度,处理[L,mid]中的修改对[mid+1,R]中的询问的影响,然后再处理区间[mid+1,R]。

题意就是有n个元素,每个元素有3个属性a,b,c。对于每一个元素问有多少个其他元素的3个属性都分别小于等于它的三维。

二维就是一个BIT求逆序对。
三维还是要先排序,然后套CDQ分治,现在有两个区间[L,mid],和[mid+1,R],由于[mid+1,R]的第一维都大于[L,mid],问题就变成了对于[mid+1,R]的每一个元素,[L,mid]有多少个元素的两维都小于它,就是一个权值BIT可以搞定了。
看起来非常简单,但我刚看到题的时候还是懵B的,听旁边的某犇吹了一会B才会一丢丢。

但这题是小于等于,并不是严格小于,原则上应该用两个数组,加入BIT时应把相同的都加进去再统计答案。但经过我的玄学思考,发现只要每次都按3关键字排序,就能解决大部分问题,但还有三维都相同的情况没有考虑。

显然三维相同答案必然也相同,而3关键字排序必然时它们排到了一起。可以由前到后去MAX,再由后往前去MAX,也许就可以了。
但还是Wa了,通过玩炉石,我发现了这可能是因为快排的不稳定性使得所有3维相同的元素的答案都不是正确答案,那么再人为控制排序的稳定性就可以A了。

复杂度大概是这样的

F(N)=F(N/2)*2+Nlog N=N(logN)^2

#include <iostream>
#include <fstream>
#include <algorithm>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>

using namespace std;
#define mmst(a, b) memset(a, b, sizeof(a))
#define mmcp(a, b) memcpy(a, b, sizeof(b))

typedef long long LL;

const int N=100100;

struct yy
{
    int a,b,c,num,now;
}f[N],g[N];

bool amp(yy x,yy y)
{
    if(x.a==y.a&&x.b==y.b)
    return x.c<y.c;
    if(x.a==y.a)
    return x.b<y.b;
    return x.a<y.a;
}

bool bmp(yy x,yy y)
{
    if(x.b==y.b&&x.c==y.c)
    return x.now<y.now;
    if(x.b==y.b)
    return x.c<y.c;
    return x.b<y.b;
}

int n,m,ans[N],h[N];
int bit[2*N];

void add(int x,int ad)
{
    for(;x<=m;x+=x&-x)
    bit[x]+=ad;
}

int sum(int x)
{
    int res=0;
    for(;x;x-=x&-x)
    res+=bit[x];
    return res;
}

void work(int l,int r)
{
    if(l==r)
    return;
    int mid=(l+r)/2;
    work(l,mid);

    for(int i=l;i<=r;i++)
    g[i]=f[i];

    sort(g+l,g+r+1,bmp);

    for(int i=l;i<=r;i++)
    if(g[i].now<=mid)
    add(g[i].c,1);
    else
    ans[g[i].num]+=sum(g[i].c);

    for(int i=l;i<=r;i++)
    if(g[i].now<=mid)
    add(g[i].c,-1);
    work(mid+1,r);
}

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&f[i].a,&f[i].b,&f[i].c);
        f[i].num=i;
    }

    sort(f+1,f+n+1,amp);
    for(int i=1;i<=n;i++)
    f[i].now=i;

    work(1,n);

    for(int i=2;i<=n;i++)
    if(f[i].a==f[i-1].a&&f[i].b==f[i-1].b&&f[i].c==f[i-1].c)
    ans[f[i].num]=max(ans[f[i].num],ans[f[i-1].num]);

    for(int i=n-1;i>=1;i--)
    if(f[i].a==f[i+1].a&&f[i].b==f[i+1].b&&f[i].c==f[i+1].c)
    ans[f[i].num]=max(ans[f[i].num],ans[f[i+1].num]);

    for(int i=1;i<=n;i++)
    h[ans[i]]++;

    for(int i=0;i<n;i++)
    printf("%d\n",h[i]);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值