专题:迷之消维——CDQ分治

CDQ分治的用途

  • 我们可能一些问题,每个状态和操作有先后的关系(即 i 的信息要从[1,i1]转移或者调用)
  • 此时我们可以用CDQ分治以 log 的复杂度来降掉一个维度。

CDQ的基本流程

  • 对于[1,8]这一个需要从小到大操作的区间我们可以给它造一个树
    这里写图片描述
  • 之后我们按先序遍历依次地遍历并更新答案
    这里写图片描述
void CDQ(int l,int r){
    if(l==r)return;
    int mid=l+r>>1;
    CDQ(l,mid);
    clear();    
    insert(L,mid);
    Query(mid+1,R); 
    CDQ(mid+1,r);
}

例题

二维LIS

  • 我们用BIT来维护
/*
    dp[i]=max{dp[x]}+1
    (x<i,mx[x]<=v[i],v[x]<=mi[i])
*/
#include<bits/stdc++.h>
using namespace std;
const int M=1e5+5;
int n,m,ans,T[M],dp[M];
struct node{
    int v,x,y,id,mx,mi;
    bool operator<(const node&A)const{
        if(x!=A.x)return x<A.x;
        if(y!=A.y)return y<A.y;
        return id<A.id;
    }
}A[M],B[M];
inline void Max(int&a,int b){if(a<b)a=b;}
inline void Min(int&a,int b){if(a>b)a=b;}
inline void add(int x,int v){
    for(;x<M;x+=x&-x){
        if(v)Max(T[x],v);
        else T[x]=0;
    }
}
inline int MAX(int x){
    int res=0;
    for(;x;x-=x&-x)Max(res,T[x]);
    return res;
}
void CDQ(int l,int r){
    if(l==r){
        Max(dp[l],1);
        return;
    }int mid=l+r>>1;
    CDQ(l,mid);
    for(int i=l;i<=r;i++){
        B[i].id=i;
        if(i<=mid)B[i].x=A[i].v,B[i].y=A[i].mx;
        else B[i].x=A[i].mi,B[i].y=A[i].v;
    }sort(B+l,B+r+1);//以x,y,id排序    
    for(int i=l;i<=r;i++){
        if(B[i].id<=mid)add(B[i].y,dp[B[i].id]);//在容器中存放h小于y的最大答案 
        else Max(dp[B[i].id],MAX(B[i].y)+1);
    }
    for(int i=l;i<=r;i++)add(B[i].y,0);//clear
    CDQ(mid+1,r);
}
int main(){
    scanf("%d %d",&n,&m);
    for(int i=1;i<=n;++i)scanf("%d",&A[i].v),A[i].mx=A[i].mi=A[i].v;
    for(int i=1,x,y;i<=m;++i){
        scanf("%d %d",&x,&y);
        Max(A[x].mx,y);     
        Min(A[x].mi,y);     
    }
    CDQ(1,n);
    for(int i=1;i<=n;++i)Max(ans,dp[i]);
    printf("%d",ans);
    return 0;
}

皇后大道东

  • n=750
  • 我们很容易得到 O(n4) Dp
  • 此时我们考虑一下它的重复操作,想到这里我们应该有了应该思路:记录前缀和。
  • 但是,问题来了如果记录一个前缀和 sum[M][M] 我们又要开一个 del[M][M][M2]
  • 虽然可以 O(n2) 地解决,可内存不足了。
  • 如果开 M4 的不行,我们考虑是否可以通过不记录 (x,y) 只记录颜色,把内存优化到 M2
  • 我们不妨改变一下扫描的顺序
    i[1  i1]

    这里写图片描述
  • 对于橙色的 dp 值即为黄色 dp 值的和。于是我们可以 O(n3) 的处理了。
dp[1][1]=1;
For(i,1,n-1){
    int res=0;
    memset(cnt,0,sizeof(cnt));
    For(j,1,m-1){
        For(k,1,i){//更新黄色的区间
            int c=col[k][j];
            Add(res,dp[k][j]);
            Add(cnt[c],dp[k][j]);
        }//用它来更新橙色的块的dp值
        int c=col[i+1][j+1];
        Add(dp[i+1][j+1],res);
        Mis(dp[i+1][j+1],cnt[c]);
    }
}ptn(dp[n][m]);  
  • 我们发现因为这里的 i 行的信息要都是从[1,i1]行的转移过来的,于是我们可以使用 CDQ 了。
    这里写图片描述
void BDQ(int l,int r){
    if(l==r)return;
    int mid=l+r>>1,res=0;
    BDQ(l,mid);
    For(j,1,m){
        /*此时我们已经把第[l,mid]行第[j-1]列的信息都处理出来了,直接转移到橙色*/
        For(i,mid+1,r)Add(dp[i][j],res),Mis(dp[i][j],cnt[col[i][j]]); 
        /*再把第[l,mid]行第[j]列的信息加过来,更新黄色的*/
        For(i,l,mid)Add(res,dp[i][j]),Add(cnt[col[i][j]],dp[i][j]);
    }
    For(i,l,r)For(j,1,m)cnt[col[i][j]]=0;
    BDQ(mid+1,r);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值