从整体二分到CDQ分治

从整体二分到CDQ分治

1.整体二分

  1. 整体二分就是将一个量(一般为答案),进行二分,对于已经满足的,就分到mid以左的部分递归进行二分,直到左边界等于右边界,如果没有满足就直接剪掉已经得到的部分,分到mid以右的部分进行递归实现。

  2. c d q cdq cdq分治最大的一个区别就是,一般来说整体二分是先处理影响再向下递归,而 c d q cdq cdq分治为先递归再处理影响。

  3. 模板 b z o j bzoj bzoj 2527

    #include
    #include
    #include
    #include
    #include
    #define mid ((nl+nr)>>1)
    #define maxn 300005
    using namespace std;
    typedef long long ll;
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    //以下为基本数组 
    int n,m,k;
    int res[maxn];
    int id[maxn],temp[maxn];
    int bel[maxn],ned[maxn];
    int z[maxn],y[maxn],t[maxn];
    vector<int>edge[maxn];
    //以下为树状数组 
    ll dat[maxn];
    int lowbit(int now)
    {
        return (now&(-now));
    }
    void update(int now,int add)
    {
        for(int i=now;i<=m;i+=lowbit(i))
        {
            dat[i]+=add;
        }
    }
    ll query(ll pos)
    {
        ll ans=0;
        for(int i=pos;i>=1;i-=lowbit(i))
        {
            ans+=dat[i];
        }
        return ans;
    }
    void insert(int now,int op)
    {
        if(z[now]<=y[now])
        {
            update(z[now],op*t[now]);
            update(y[now]+1,-op*t[now]);
        }
        else
        {
            update(z[now],op*t[now]);
            update(1,op*t[now]);
            update(y[now]+1,-op*t[now]);
        }
    }
    ll sum[maxn];
    //dfs(当前处理到的询问,可能成为答案的区间)
    void dfs(int ql,int qr,int nl,int nr)
    {
        if(ql>qr) return;
        if(nl==nr)
        {
            for(int i=ql;i<=qr;i++)
            {
                res[id[i]]=nl;
            }
            return;
        }
         
        for(int i=nl;i<=mid;i++)
        {
            insert(i,1);
        }
         
        for(int i=ql;i<=qr;i++)//这里要注意是ql到qr,不要超了,否则复杂度就过大了。 
        {
            sum[id[i]]=0;
        }
         
        for(int i=ql;i<=qr;i++)
        {
            int len1=edge[id[i]].size();
            for(int j=0;j<len1;j++)
            {
                sum[id[i]]+=query(edge[id[i]][j]);
                if(sum[id[i]]>=ned[id[i]]) break;
            }
        }
        int cnt=0;
        for(int i=ql;i<=qr;i++)//满足的放一边,不满足的放另外一边,因此要先统计 
        {
            if(sum[id[i]]>=ned[id[i]])
            cnt++;
        }
        int l1=ql,l2=ql+cnt;
        for(int i=ql;i<=qr;i++)
        {
            if(sum[id[i]]>=ned[id[i]])
            {
                temp[l1++]=id[i];
            }
            else
            {
                temp[l2++]=id[i];
                ned[id[i]]-=sum[id[i]];//这里记得减去之前的 
            }
        }
         
        for(int i=ql;i<=qr;i++)
        id[i]=temp[i];
         
        for(int i=nl;i<=mid;i++)
        {
            insert(i,-1);//记得清除之前的更新 
        }
        dfs(ql,ql+cnt-1,nl,mid);
        dfs(ql+cnt,qr,mid+1,nr);
    }
    int main()
    {
        n=read();m=read();
        for(int i=1;i<=m;i++)
        {
            int x;
            x=read();
            edge[x].push_back(i);
        }
        for(int i=1;i<=n;i++)
        {
            ned[i]=read();
            id[i]=i;
        }
        k=read();
        for(int i=1;i<=k;i++)
        {
            z[i]=read();
            y[i]=read();
            t[i]=read();
        }
         
        k++;
        z[k]=1;y[k]=m;t[k]=1000000009;
        dfs(1,n,1,k);
         
        for(int i=1;i<=n;i++)
        {
            if(res[i]!=k) printf("%d\n",res[i]);
            else printf("NIE\n");
        }
    }
    
  4. 例题

    1. b z o j bzoj bzoj 2527 入门题,每次下一半的流星雨,看每个位置下够没下够,够了向左递归,没够向右递归。

    2. b z o j bzoj bzoj 3110 有区间加数的k大数查询,对答案的值进行二分,修改操作跟着一起二分,注意内部要按时间排序,因为修改操作会对答案有影响。

    3. b z o j bzoj bzoj 4009 给定树上两种路径,第二种路径有权值,求第一种路径包含的第二种路径里权值的第k大。

      这是一道神仙题,思路我觉得很有推广的余地。

      把第一种路径看成点 ( d f n [ x ] , d f n [ y ] ) (dfn[x],dfn[y]) (dfn[x],dfn[y]) 这里假设 d f n [ x ] < d f n [ y ] dfn[x]dfn[x]<dfn[y]

      如果第一种路径 x − y x-y xy包含了第二种路径 x 1 − y 1 x_1 - y_1 x1y1,且第二种路径的 l c a lca lca不是它的某个端点,那么必然有

      i n [ x 1 ] ≤ d f n [ x ] ≤ o u t [ x 1 ]   & &   i n [ y 1 ] ≤ d f n [ y ] ≤ o u t [ y 1 ] in[x1]\leq dfn[x]\leq out[x_1]\ \&\&\ in[y_1]\leq dfn[y] \leq out[y_1] in[x1]dfn[x]out[x1] && in[y1]dfn[y]out[y1]

      如果第一种路径 x − y x-y xy包含了第二种路径 x 1 − y 1 x_1 - y_1 x1y1,且第二种路径的 l c a lca lca是它的某个端点 x x x,我们设 w 为 x 1 到 y 1 w为x_1到y_1 wx1y1路径上的第一个儿子,那么下列两个式子一定有一个成立。

      1 ≤ d f n [ x ] ≤ d f n [ w ]   & &   i n [ y 1 ] ≤ d f n [ y ] ≤ o u t [ y 1 ] 1\leq dfn[x]\leq dfn[w]\ \&\&\ in[y_1]\leq dfn[y]\leq out[y_1] 1dfn[x]dfn[w] && in[y1]dfn[y]out[y1] 起点在 x x x的左边,终点在y的底下

      i n [ y 1 ] ≤ d f n [ x ] ≤ o u t [ y 1 ]   & &   d f n [ w ] + 1 ≤ d f n [ y ] ≤ n in[y_1]\leq dfn[x]\leq out[y_1]\ \&\&\ dfn[w]+1\leq dfn[y]\leq n in[y1]dfn[x]out[y1] && dfn[w]+1dfn[y]n 起点在y的底下,终点在x的右边。

      这样我们就成功把第二种路径拆成了一个,或者两个互不相交的矩形,现在要求的是对一个点来说,覆盖它的矩形里的权值第 k k k大,第二种路径按权值排序,整体二分之后扫描线统计即可。

2.CDQ分治

  1. CDQ分治的精髓就在于,最初是按某个维度X排好序,先递归处理左边的,再递归处理右边的,递归回来之后首先有之前排好序的性质,即左边的X小于等于右边的X,又有了左边的每个元素是按照另外一维Y排好序的。这样递归上来之后,接着依赖于这种排好序的性质,再处理左边对右边的影响,顺便对Y进行排序再往回回溯。
  2. CDQ一般求解二、三维偏序问题。
  3. 代码 b z o j bzoj bzoj 3262陌上花开
#include
#include
#include
#include
#define maxn 200005
#define mid ((l+r)>>1)
using namespace std;
struct nod
{
    int x,y,z,id;
    nod()
    {
        x=0;
        y=0;
        z=0;
        id=0;
    }
}a[maxn],b[maxn];
int n,k;
int dat[maxn];
int ans[maxn];
bool operator<(nod a,nod b)
{
    return a.x!=b.x ? a.x<b.x : (a.y!=b.y ? a.y<b.y : a.z<b.z);
}
bool operator==(nod a,nod b)
{
    return a.x==b.x && a.y==b.y && a.z==b.z;
}
int lowbit(int now)
{
    return (now&(-now));
}
void add(int x,int val)
{
    for(int i=x;i<=k;i+=lowbit(i))
        dat[i]+=val;
}
int query(int x)
{
    int ans=0;
    for(int i=x;i;i-=lowbit(i))
        ans+=dat[i];
    return ans;
}
void cdq(int l,int r)
{
    if(l>=r) return;
    cdq(l,mid);
    cdq(mid+1,r);
    int s1=l,s2=mid+1;
    while(s1<=mid || s2<=r)
    {
        if(s2>r || (s1<=mid && a[s1].y<=a[s2].y))
        {
            add(a[s1].z,1);
            b[l+(s1-l)+(s2-(mid+1))]=a[s1];
            s1++;
        }
        else
        {
            ans[a[s2].id]+=query(a[s2].z);
            b[l+(s1-l)+(s2-(mid+1))]=a[s2];
            s2++;
        }
    }
    for(int i=l;i<=mid;i++)
    add(a[i].z,-1);
    for(int i=l;i<=r;i++)
    a[i]=b[i];
}
int cnt[maxn];
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].z);
        a[i].id=i;
    }
    sort(a+1,a+1+n);
     
    for(int i=n;i>=1;i--)
    {
        if(a[i]==a[i+1])
            ans[a[i].id]=ans[a[i+1].id]+1;
        //如果两个元素完全相等的话,它右边的也会对它的答案产生贡献,需要加上。
    }
     
    cdq(1,n);
    for(int i=1;i<=n;i++)
    cnt[ans[i]]++;
    for(int i=0;i<n;i++)
    printf("%d\n",cnt[i]);  
} 
  1. b z o j bzoj bzoj 3262陌上花开 做法都在上文讲了,这里处理左边对右边影响的时候,利用了双指针的思路,先移动左边的 s 1 s_1 s1保证右边的 s 2 s_2 s2所对应的y大于等于左边的,接着通过树状数组统计第三维。不过要注意如果两个元素完全相等的话,它右边的也会对它的答案产生贡献,需要加上。

你可能感兴趣的:(从整体二分到CDQ分治)