CDQ 从二维偏序到三维偏序 从逆序对到动态逆序对 详解

CDQ 从二维偏序到三维偏序 从逆序对到动态逆序对详解

    • CDQ 分治思想
    • 二维偏序 (逆序对)
    • 三维偏序 (动态逆序对)

CDQ 分治思想

CDQ分治,顾名思义,这个思想重点在于如何
分:将问题从整体分解成几个小部分(一般是两个)。
治:考虑各部分之间的贡献并计算,把部分合并到整体。

二维偏序 (逆序对)

二维偏序模板问题:
一个平面上,给出n个点(X,Y)。
定义: 设 A,B 两点,如果A.x
求每个点的权值。

最方便的解决办法当然是树状数组,对其中一维(x)排序,另一维(y)在以x的顺序插入树状数组中时,查询答案即可。
这里不累述。

还有一种方法就是CDQ分治:
附上自己写的伪代码:

void CDQ(int l,int r)
{
    if(r-l<1) return; // 结束条件
    
    /*
    分 
    预处理排序x;
    此时二分处理时左右区间左区间x全部小于右区间的x
    所以右区间不会对左区间产生贡献
    */
    int m = (l+r)>>1;  // 二分处理
    CDQ(l,m);  //  递归处理左边界
    CDQ(m+1,r);  //  递归处理右边界
    
    /*
    治
    合并区间时,由于递归处理,此时左右两部分的y是有序的  见下
    而且在分的时候x是有序的
    所以只有左区间会对右区间产生贡献
    我们便可以在线性时间内处理左区间对右区间的贡献并以y排好序
    */
    int p = l , q = m+1 , t = 0;
    while(q<=r || p<=m){
        /*
        指针移动法
        处理左区间对右区间的贡献;
        并把排好序的结构体数组付给中间数组temp
        */
    }
    t = 0;
    for(int i=l;i<=r;i++)   // 整体排序 所以说上面合并时已经有序
        z[i] = temp[++t];
}

给出例题 : HDU 1541

三维偏序 (动态逆序对)

在刚刚二维偏序的基础上我们给定n个点加入平面的顺序,每个点只考虑当前时间的点对它产生贡献,不考虑后加的点。
这时,应该扩充平面的点一个时间元素t,设定第一次加入点时t=1,往后时间t增加,一直到n。
这时A对B产生贡献,不仅需要A.x
这便是一个三维偏序问题

那么我们如何处理三维偏序
我们已经知道 分治+排序 与 树状数组+排序 可以解决二维偏序问题, 排序一维+分治一维 = 二维排序一维 + 树状数组一维 = 二维那我们能否 排序一维 + CDQ一维 + 树状数组一维 = 三维 ?
不可以
答案当然是可以

我大致讲一下: 首先排序使x有序,然后进行CDQ分治,其基本还是和二维偏序时操作一样,但是在整体合并的处理上,不是直接查找,而是以y的顺序插入到树状数组当中,然后以t查找。

这是大致思维
更高维的偏序问题我未接触过,但是应该和我上述一样,升维即可,树套树,CDQ套CDQ等等…

动态逆序对 HYSBZ - 3295 包含 AC代码CDQ 从二维偏序到三维偏序 从逆序对到动态逆序对 详解_第1张图片

#include
#define ll long long
using namespace std;
const int maxn=1e5+5, maxm=5e4+5;
int n,m;

struct node
{
    int va,pos,t;
};
node z[maxn],tem[maxn];

int C[maxn];
int lowbit(int x)
{
    return x&-x;
}
void add(int x,int d)
{
    while(x<=n)
    {
        C[x]+=d, x+=lowbit(x);
    }
}
int sum(int x)
{
    int ans = 0;
    while(x)
    {
        ans+=C[x], x-=lowbit(x);
    }
    return ans;
}


ll ans[maxn];
void CDQ(int l,int r)
{
    if(l>=r)
        return;
    int mid = (l+r)>>1;
    
    int q,p,k;
    p = l, q = mid+1;
    for(int i=l; i<=r; i++)  // 根据t的大小把点二分为左区间与右区间
        if(z[i].t<=mid)
            tem[p++] = z[i];
        else
            tem[q++] = z[i];
    
    
    for(int i=l; i<=r; i++)
        z[i] = tem[i];
        
        
    p = l, q = mid+1, k = 0;  // 正向求贡献
    while(p<=mid || q<=r)
    {
        if(q>r || p<=mid&&z[p].pos<z[q].pos)
        {
            add( z[p].va, 1 ) ;
            k++  ;
            p++;
        }
        else
        {
            ans[z[q].t] += (k - sum(z[q].va)) ;
            q++;
        }
    }
    
    
    for(int i=l; i<=mid; i++)   // 清除树状数组
        add(z[i].va,-1);
        
        
    p = mid, q = r;      // 反向求贡献
    while(p>=l || q>mid)
    {
        if(q<=mid || p>=l&&z[p].pos>z[q].pos)
        {
            add(z[p].va, 1) ;
            p-- ;
        }
        else
        {
            ans[z[q].t] += sum(z[q].va) ;
            q-- ;
        }
    }
    
    
    for(int i=l; i<=mid; i++)   // 清除树状数组
        add(z[i].va,-1);
        
    CDQ(l,mid); 
    CDQ(mid+1,r);
}

int a[maxn],ma[maxn];
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1; i<=n; i++)
        cin>>a[i];
    for(int i=1; i<=m; i++)
    {
        int b;
        cin>>b;
        ma[b] = i;
    }
    int k = 0;
    for(int i=1; i<=n; i++)
    {
        int b = ma[a[i]];
        if(b)
        {
            z[i] = node{a[i],i,n-b+1};
        }
        else
            z[i] = node{a[i],i,++k};
    }
    CDQ(1,n);
    for(int i=2; i<=n; i++)
        ans[i] += ans[i-1];
    for(int i=n; i>n-m; i--)
        cout<<ans[i]<<endl;
}

不想拿金的Acmer不是好的Acmer

你可能感兴趣的:(ACM)