COGS2580:[HZOI 2015]偏序 II (三层CDQ分治+树状数组)

题目传送门:http://www.cogs.pro/cogs/problem/problem.php?pid=2580


题目分析:又找了一道CDQ分治的裸题,不到30min就切掉了,感觉只要用CDQ分治n维偏序都不成问题……
这题中序列的每个元素有5个属性:编号,a,b,c,d;我们要求有多少对(i,j)使得i的五个属性都小于j。我们不妨先按编号排序,然后进行第一层CDQ,考虑i在左边,j在右边时对答案的贡献,这里要求i的另外四个属性小于j。如果这是三维偏序的话,我们将左半部分的元素变成插入操作,将右半部分的元素变成查询操作,然后将它们堆在一起按第二维排序,以第二维从小到大为时间戳插入以第三维为键值的树状数组中即可。
现在五维偏序也类似,我们将左边的元素变为插入操作,右边的元素变成查询操作(保证所有插入操作的第一维都小于所有查询操作的第一维),然后将它们堆在一起按第二维排序,再进行第二层CDQ。我们又将操作序列划分成两半,计算左半部分的插入对右半部分的查询的影响(保证插入操作的第二维小于查询的第二维),于是又将它们单独拎出来按第三维排一次序,得到一个新序列,用同样的方法进行第三层CDQ。按第四维排完序之后开一个以第五维为键值的树状数组进行插入和查询即可,时间复杂度 O(nlog4(n))


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=50010;

struct data
{
    int id,a,b,c,d;
    bool p;
} num[4][maxn];
int cnt[4];

int bit[maxn];
int n,ans=0;

bool Comp1(data x,data y)
{
    return x.a<y.a;
}

bool Comp2(data x,data y)
{
    return x.b<y.b;
}

bool Comp3(data x,data y)
{
    return x.c<y.c;
}

void Add(int x,int v)
{
    while (x<=n)
    {
        bit[x]+=v;
        x+=(x&(-x));
    }
}

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

void Work()
{
    for (int i=1; i<=cnt[3]; i++)
        if (!num[3][i].p) Add(num[3][i].d,1);
        else ans+=Sum(num[3][i].d);
    for (int i=1; i<=cnt[3]; i++)
        if (!num[3][i].p) Add(num[3][i].d,-1);
}

void CDQ3(int L,int R)
{
    if (L==R) return;
    int mid=(L+R)>>1;
    cnt[3]=0;
    for (int i=L; i<=mid; i++) if (!num[2][i].p) num[3][ ++cnt[3] ]=num[2][i];
    for (int i=mid+1; i<=R; i++) if (num[2][i].p) num[3][ ++cnt[3] ]=num[2][i];
    if (cnt[3])
    {
        sort(num[3]+1,num[3]+cnt[3]+1,Comp3);
        Work();
    }
    CDQ3(L,mid);
    CDQ3(mid+1,R);
}

void CDQ2(int L,int R)
{
    if (L==R) return;
    int mid=(L+R)>>1;
    cnt[2]=0;
    for (int i=L; i<=mid; i++) if (!num[1][i].p) num[2][ ++cnt[2] ]=num[1][i];
    for (int i=mid+1; i<=R; i++) if (num[1][i].p) num[2][ ++cnt[2] ]=num[1][i];
    if (cnt[2])
    {
        sort(num[2]+1,num[2]+cnt[2]+1,Comp2);
        CDQ3(1,cnt[2]);
    }
    CDQ2(L,mid);
    CDQ2(mid+1,R);
}

void CDQ1(int L,int R)
{
    if (L==R) return;
    int mid=(L+R)>>1;
    cnt[1]=0;
    for (int i=L; i<=mid; i++) num[1][ ++cnt[1] ]=num[0][i],num[1][ cnt[1] ].p=false;
    for (int i=mid+1; i<=R; i++) num[1][ ++cnt[1] ]=num[0][i],num[1][ cnt[1] ].p=true;
    sort(num[1]+1,num[1]+cnt[1]+1,Comp1);
    CDQ2(1,cnt[1]);
    CDQ1(L,mid);
    CDQ1(mid+1,R);
}

int main()
{
    freopen("partial_order_two.in","r",stdin);
    freopen("partial_order_two.out","w",stdout);

    scanf("%d",&n);
    for (int i=1; i<=n; i++) num[0][i].id=i;
    for (int i=1; i<=n; i++) scanf("%d",&num[0][i].a);
    for (int i=1; i<=n; i++) scanf("%d",&num[0][i].b);
    for (int i=1; i<=n; i++) scanf("%d",&num[0][i].c);
    for (int i=1; i<=n; i++) scanf("%d",&num[0][i].d);

    CDQ1(1,n);
    printf("%d\n",ans);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值