[BZOJ3262]陌上花开(cdq分治+讲解+小结)

题目:

我是超链接

题解:

那么这道题就是三维偏序的题啦
具体情况下见普及向咯
要特别注意这里是权值树状数组,所以最大值要到k而并非n
并且如果像这样写在cdq中加一个merge,就免去了快拍的logn,总复杂度是 O(nlogn)

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=100005;
struct hh{int x,y,z,id,ans;}f[N],jl[N];
int c[N*2],cnt[N],n,k,ans[N];
int cmp(hh a,hh b){return (a.x<b.x)||(a.x==b.x && a.y<b.y)||(a.x==b.x && a.y==b.y && a.z<b.z);}
void add(int loc,int v)
{
    for (int i=loc;i<=k;i+=i&(-i)) //
      c[i]+=v;
}
int qurry(int loc)
{
    int ans=0;
    for (int i=loc;i>=1;i-=i&(-i))
      ans+=c[i];
    return ans;
}
void cdq(int l,int r)
//进去的时候已经按照a排好序,出来的时候已经按照b排好序,反正右边的a肯定比左边的大啊,然后在c的位置+1,统计b[i]之前的和就是比c大的个数
{
    if (l>=r) return;
    int mid=(l+r)>>1;
    cdq(l,mid);cdq(mid+1,r);
    int t1=l,t2=mid+1;
    for (int i=l;i<=r;i++)
    {
        if ((t1<=mid && f[t1].y<=f[t2].y) || t2>r)
        {
            jl[i]=f[t1];
            add(f[t1].z,1);
            t1++;
        }
        else 
        {
            jl[i]=f[t2];
            jl[i].ans+=qurry(f[t2].z);
            t2++;
        }
    }
    for (int i=l;i<=mid;i++) add(f[i].z,-1);
    for (int i=l;i<=r;i++) f[i]=jl[i];
}
int main()
{
    scanf("%d%d",&n,&k);
    for (int i=1;i<=n;i++) scanf("%d%d%d",&f[i].x,&f[i].y,&f[i].z);
    sort(f+1,f+n+1,cmp);
    for (int i=1;i<=n;i++) f[i].id=i;
    cdq(1,n);
    for (int i=n-1;i>=1;i--)
      if (f[i].x==f[i+1].x && f[i].y==f[i+1].y && f[i].z==f[i+1].z)
        f[i].ans=max(f[i].ans,f[i+1].ans);
    for (int i=1;i<=n;i++)
      ++cnt[f[i].ans];
    for (int i=0;i<n;i++) printf("%d\n",cnt[i]);
}

普及向:

先回忆一下在我们求逆序对的时候是怎样的?

mergesort(l,mid);
mergesort(mid+1,r);
    int t1=l; 
    int t2=mid+1;
    for (int i=l;i<=r;i++)
    {
        if ((t1<=mid&&a[t1]<=a[t2])||t2>r) {
            b[i]=a[t1];
            t1++;
        }
        else
        {
            b[i]=a[t2]; 
            ans+=(mid-t1+1);
            t2++;
        } 
    }
    for (int i=l;i<=r;i++) a[i]=b[i];

我们看看这个逆序对的过程(除去排序的过程),实际上就是对于第一维【位置】(已经排好序了),我们在[l,r]上排好第二维【大小】,用左边更新右边
实际上这就是一个二维偏序问题,是CDQ分治的缩影

那么我们看看三维偏序问题啦
给定N个有序三元组(a,b,c),求对于每个三元组(a,b,c),有多少个三元组(a2,b2,c2)满足a2 < a且b2 < b且c2 < c。

类似二维偏序问题,先按照a元素从小到大排序,忽略a元素的影响。然后CDQ分治,按照b元素从小到大的顺序进行归并操作。但是这时候没办法像 求逆序对 一样简单地统计个数了,c元素如何处理呢?

这时候比较好的方案就是借助权值树状数组。每次从右边的序列中取出三元组(a,b,c)时,对树状数组查询c值小于(a,b,c)的三元组有多少个;每次从左边序列取出三元组(a,b,c)的时候,根据c值在树状数组中进行修改。注意,每次使用完树状数组记得把树状数组归零!
 
具体过程就是这样,其实精简的话是这样的:分而治之,总排序减去一维,分治时排序减去一维,最后用权值树状数组,左边修改,右边询问,用左边更新右边

对于经典的多维偏序问题和多维数据结构的查询和修改,我们可以用一步步“降维”的方式解决。排序,数据结构,CDQ分治都是我们降维的工具。

CDQ分治还有其他很多强大的功能,比如多重嵌套CDQ分治,用CDQ分治加速动态规划等等。总的来说就是可以顶一层数据结构,降维用。

小结:

cdq分治就告一段落了,那么我总结一下呗
cdq分治——一维分治,一维排序,一维树状数组(可求前缀和,可求前面最大)
cdq分治单独来用的话用于解决二维/三维偏序问题,主要是观察几个元素的关系,以及要弄明白【进去的时候按照什么排,出来的时候按照什么排】,并且要搞清楚前面的修改对后面有没有影响,比如说像这道题加入相对大小的值就对后面没什么影响,所以cdq(mid+1,r)可以放在前面;但是像一些更新最优值的呀,就一定要按照:【更新左边,用左边更新右边,更新右边】这样的顺序
cdq分治结合dp的话,一定要是顺次dp,这里的dp有两种类型,一种就是比较每个元素大小的问题(如BZOJ2253
另一种比较常见的就是画柿子然后转化为斜率优化dp,其实仔细来讲应该是截距优化dp,我们按照左边排序的时候维护一个凸包,右边按照已经排好序的斜率查询,这里的柿子应该画成:
Aj=BiCj+Di ,如果写暴力的话,j∈[1..i-1]

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值