整体二分 && CDQ分治

前言

最近学习了一下CDQ分治和整体分治,在这里做了一下整理,内容和代码有所借鉴,如果您有所不满,请通知我,我会删除。

CDQ分治

离线的做法,可以避免使用树套树这种数据结构。
通常要和树状数组一同使用。
流程
1. 按时间顺序处理出操作,并表好号。
2. 在CDQ分治中先处理出左右两个区间。
3. 然后处理出左区间对右区间的影响。
怎么理解呢? 因为右区间内的影响已经在子问题中处理出来了,所以只剩下左区间对右区间的影响还没有处理。
特别的需要注意CDQ分治只能处理离线(可能可以在线,但我不会)。


可以通过求逆序对(即二维偏序)来理解一下CDQ分治。
如果学过归并求逆序对的话,可以拿它来理解。


最最模板的是单点修改区间查询。
可能会说了:可以用树状数组秒过啊。
但是我们就是要搞搞事情用CDQ分治做一下子。


维护每一个的位置id最为第一权值,CDQ中维护id升序。
首先 可以把初始化当做修改可以方便处理
其次 要想要快速的查询出区间和的话,是可以差分的。
所以 将每一个查询拆分成两部分(a,b)a以内的减掉,b以内的加上
在CDQ中 记录左区间的修改量,处理右区间的查询。

代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=500000+10;
struct node
{
    int s,c,m,id;
    bool operator < (const node &o)const
    {
        if(s!=o.s) return s<o.s;
        else if(c!=o.c) return c<o.c;
        else return m<o.m;
    }
}a[maxn],tt[maxn],b[maxn],c[maxn];
int n,k,level[maxn],tree[maxn],ans[maxn];
int lowbit(int x)
{
    return x&(-x);
}
void modify(int p,int d)
{
    while(p<=k)
    {
        tree[p]+=d;
        p+=lowbit(p);
    }
}
int query(int p)
{
    int ans=0;
    while(p)
    {
        ans+=tree[p];
        p-=lowbit(p);
    }
    return ans;
}
void cdq(int l,int r)
{
    if(l>=r) return;
    int mid=(l+r)>>1;
    cdq(l,mid); cdq(mid+1,r);
    int tot=0,p1=l,p2=mid+1;
    for(int i=l; i<=r; i++)
    {
        if((p1<=mid && (a[p1].c<a[p2].c || (a[p1].c==a[p2].c && a[p1].m<=a[p2].m)))|| p2>r)
        {
            tot++; tt[tot]=a[p1]; b[i]=a[p1]; modify(a[p1].m,1); p1++; //左区间维护树状数组,并记录下来,方便清空
        }
        else
        {
            level[a[p2].id]+=query(a[p2].m); b[i]=a[p2]; p2++;
        }
    }
    for(int i=1; i<=tot; i++)
    {
        modify(tt[i].m,-1);
    }
    for(int i=l; i<=r; i++)
    {
        a[i]=b[i];
    }
}
int main()
{
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);
    scanf("%d %d",&n,&k);
    for(int i=1; i<=n; i++)
    {
        scanf("%d %d %d",&a[i].s,&a[i].c,&a[i].m); 
    }
    sort(a+1,a+n+1);
    for(int i=1; i<=n; i++)
    {
        a[i].id=i;
        c[i]=a[i];
    }
    cdq(1,n);
    for(int i=n; i>=1; i--)
    {
        if(c[i].s==c[i+1].s && c[i].c==c[i+1].c && c[i].m==c[i+1].m)
            level[i]=max(level[i],level[i+1]);
        ans[level[i]]++;
    }//因为有完全一样的,所以要这么处理,选出一样的当中最大的一个。
    for(int i=0; i<n; i++)
    {
        printf("%d\n",ans[i]);
    }
    return 0;
}

整体二分

可以看一下这位dalao
说一下自己的理解吧。
整体二分就是所有的查询和修改一同处理,然后分类,哪一类的答案是….. 要做的就是分出这些类,并且可以记住一些信息降低常数。
带修改的区间第k大
[COGS 257] 动态排名系统
直接上代码

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=1000000;
struct node
{
    int x,y,type,id,k,cur;
}a[maxn],c1[maxn],c2[maxn];
int tot,tree[maxn],totx,n,m,data[maxn];
int ans[maxn],type,tmp[maxn];
int T;
int lowbit(int x)
{
    return x&(-x);
}
void modify(int x,int y)
{
    while(x<=n)
    {
        tree[x]+=y;
        x+=lowbit(x);
    }
}
int query(int x)
{
    int ans=0;
    while(x)
    {
        ans+=tree[x];
        x-=lowbit(x);
    }
    return ans;
}
void entire_divide(int L,int R,int l,int r)
{
    if(L>R) return;
    if(l>=r)
    {
        //对于L,R找到了答案l。
        for(int i=L; i<=R; i++)
        {
            if(a[i].type==3) ans[a[i].id]=l;
        }
        return;
    }
    else
    {
        int mid=(l+r)>>1;
        int l1=0,l2=0;
        for(int i=L; i<=R; i++)
        {
        //处理处来小于mid的数有多少,记录在树状数组中。
            if(a[i].type==1 && a[i].y<=mid) modify(a[i].x,1);//有这个点
            else if(a[i].type==2 && a[i].y<=mid) modify(a[i].x,-1);//删除这个点
            else if(a[i].type==3) tmp[i]=query(a[i].y)-query(a[i].x-1);//查分查询[x,y]中有多少小于mid的数。
        }
        //清空
        for(int i=L; i<=R; i++)
        {
            if(a[i].type==1 && a[i].y<=mid) modify(a[i].x,-1);
            else if(a[i].type==2 && a[i].y<=mid) modify(a[i].x,1);
        }
        //分类。
        for(int i=L; i<=R; i++)
        {
            if(a[i].type==3)
            {
                if(a[i].cur+tmp[i]<a[i].k)//不够,分到右区间
                {
                    l2++; a[i].cur+=tmp[i]; c2[l2]=a[i];
                    //累加,减少查询比mid小的数值。 
                }
                else
                    c1[++l1]=a[i];//够了,分到左区间
            }
            else
            {
                if(a[i].y<=mid) c1[++l1]=a[i];//修改的比mid小
                else c2[++l2]=a[i];//比mid大的放到右边。
            }
        }
        for(int i=L; i<L+l1; i++)
        {
            a[i]=c1[i-L+1];
        }
        for(int i=L+l1; i<L+l1+l2; i++)
        {
            a[i]=c2[i-l1-L+1];
        }
        entire_divide(L,L+l1-1,l,mid); entire_divide(L+l1,R,mid+1,r);
        //处理分好类的区间。
    }

}
int main()
{
    freopen("test.in","r",stdin);
    freopen("test.out","w",stdout);
    scanf("%d",&T);
    while(T--)
    {
        memset(a,0,sizeof(a));
        scanf("%d %d",&n,&m);
        tot=0,totx=0;
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&data[i]);
            tot++; a[tot]=(node){i,data[i],1,0,0,0};//将初始数据当做修改
        }
        for(int i=1; i<=m; i++)
        {
            char ch=getchar();
            while(ch!='Q' && ch!='C') ch=getchar();
            if(ch=='Q')
            {
                int x,y,w; scanf("%d %d %d",&x,&y,&w);
                tot++; totx++;
                a[tot]=(node){x,y,3,totx,w,0};
            }
            else
            {
                int x,y; scanf("%d %d",&x,&y);
                //修改分为两部分,2是删除,1是插入。
                tot++; a[tot]=(node){x,data[x],2,0,0,0};
                tot++; a[tot]=(node){x,y,1,0,0,0};
                data[x]=y;
            }
        }
        entire_divide(1,tot,0,1000000000);
        for(int i=1; i<=totx; i++)
        {
            printf("%d\n",ans[i]);
        }
    }
    return 0;
}

尾言

欢迎dalao指教。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值