cdq分治学习笔记

1.作用

可以用来搞一些离线的题目,用于代替复杂的数据结构入树套树。

2.做法

1.分治左边区间。

2.计算左边区间对右边区间答案的贡献。

3.分治右边区间。

4.将当前区间排序。

5.return。

3.例题

1.树状数组

单点修改区间查询。

将所有操作记录cdq分治。

每次分治时递归完了如果在左区间并且是添加操作就反手将sum+=jia,如果是询问就把ans[]+=sum。

莫得代码。

2.经典三维偏序。

【模板】三维偏序(陌上花开)

设三维分别为a,b,c。

1.第一维排好序后分治,第二位用归并排序保证在这个地方询问时前面的b比当前的b小,然后第三位用树状数组压入,查询c比当前晓得。

2.只有在a在左区间时才执行添加操作,a在右区间时执行询问操作,这样就保证了询问的a永远大于加入的a。

3.总结就是第一维靠分治保证小于,第二维靠归并排序,第三位靠树状数组。

4.递归结束时要清空树状数组。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int n,k,shu[200005],dui[100005],cnt,daan[100005],ans[100005];
struct pigu
{
    int xx,yy,zz,id;
}a[100005];
int cmp1(pigu x,pigu y)
{
    if(x.xx!=y.xx)
        return x.xx<y.xx;
    if(x.yy!=y.yy)
        return x.yy<y.yy;
    if(x.zz!=y.zz)
        return x.zz<y.zz;
}
int cmp2(pigu x,pigu y)
{
    if(x.yy!=y.yy)
        return x.yy<y.yy;
    if(x.zz!=y.zz)
        return x.zz<y.zz;
    return x.xx<y.xx;
}
int lowbit(int x)
{
    return x&(-x);
}
void cdq(int l,int r)
{
    if(l==r)
        return;
    int mid=(l+r)>>1;
    cdq(l,mid);
    cdq(mid+1,r);
    sort(a+l,a+r+1,cmp2);
    for(int i=l;i<=r;i++)
        if(a[i].xx<=mid)
        {
            int now=a[i].zz;
            while(now<=k)
            {
                shu[now]++;
                now+=lowbit(now);
            }
        }
        else
        {
            int now=a[i].zz;
            while(now)
            {
                daan[a[i].id]+=shu[now];
                now-=lowbit(now);
            }
        }
    for(int i=l;i<=r;i++)
        if(a[i].xx<=mid)
        {
            int now=a[i].zz;
            while(now<=k)
            {
                shu[now]--;
                now+=lowbit(now);
            }
        }
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&a[i].xx,&a[i].yy,&a[i].zz);
        a[i].id=i;
    }
    sort(a+1,a+n+1,cmp1);
    for(int i=1;i<=n;)
    {
        int j=i+1;
        while(a[i].xx==a[j].xx&&a[i].yy==a[j].yy&&a[i].zz==a[j].zz)
            j++;
        ++cnt;
        while(i<j)
        {
            dui[a[i].id]=a[j-1].id;
            i++;
        }
    }
    for(int i=1;i<=n;i++)
        a[i].xx=i;
    cdq(1,n);
    for(int i=1;i<=n;i++)
    {
        ans[daan[dui[a[i].id]]]++;
    }
    for(int i=0;i<n;i++)
        printf("%d
",ans[i]);
    return 0;
}
/*
3 3
1 2 3
0 1 2
1 2 3
*/
View Code

3.P2163 [SHOI2007]园丁的烦恼

给出了一些点,一堆询问求一个矩形内有几个点。

一开始感觉是三维偏序,要两个log,结果发现时间没用,就成二维偏序了。

cdq分治,二维偏序,将询问拆成四个(跟矩阵前缀和差不多)就可以了。

排序时先考虑y再考虑是询问还是添加。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=5000005;
int n,m,cnt,ma,ans[N];
struct pigu
{
    int pan,x,y,zai,xp;
    friend bool operator <(pigu x,pigu y)
    {
        return x.y<=y.y;
    }
}a[N],hu[N];
inline bool cmp(pigu x,pigu y)
{
    return x.x==y.x?(x.y==y.y?x.pan<y.pan:x.y<y.y):x.x<y.x;
}
inline int read()
{
    char c=getchar();
    int x=0,f=1;
    while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)) {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*f;
}
inline void solve(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    solve(l,mid);solve(mid+1,r);
    int o=l,ll=l,mm=mid+1,daan=0;
    while(ll<=mid&&mm<=r)
    {
        if(a[ll]<a[mm])
        {
            if(a[ll].pan==1) daan++;
            hu[o++]=a[ll++];
        }
        else
        {
            if(a[mm].pan==2) ans[a[mm].zai]+=a[mm].xp*daan;
            hu[o++]=a[mm++];
        }
    }
    while(ll<=mid) hu[o++]=a[ll++];
    while(mm<=r)
    {
        if(a[mm].pan==2) ans[a[mm].zai]+=a[mm].xp*daan;
        hu[o++]=a[mm++];
    }
    for(int i=l;i<=r;i++) a[i]=hu[i];
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
    {
        a[++cnt].x=read()+1;a[cnt].y=read()+1;ma=max(ma,a[cnt].y);
        a[cnt].pan=1;
    }
    for(int i=1,x,y,xx,yy;i<=m;i++)
    {
        x=read()+1;y=read()+1;xx=read()+1;yy=read()+1;
        a[++cnt].pan=2;a[cnt].x=x-1,a[cnt].y=y-1;a[cnt].zai=i;a[cnt].xp=1;
        a[++cnt].pan=2;a[cnt].x=x-1;a[cnt].y=yy;a[cnt].zai=i;a[cnt].xp=-1;
        a[++cnt].pan=2;a[cnt].x=xx;a[cnt].y=y-1;a[cnt].zai=i;a[cnt].xp=-1;
        a[++cnt].pan=2;a[cnt].x=xx;a[cnt].y=yy;a[cnt].zai=i;a[cnt].xp=1;
    }
    sort(a+1,a+cnt+1,cmp);
    solve(1,cnt);
    for(int i=1;i<=m;i++) cout<<ans[i]<<"
";
}    
View Code

4.P4390 [BOI2007]Mokia 摩基亚

就是上一个题增加了一手修改,就再加上时间,就成了三维偏序,每个询问还是拆成四个:查询时间小于此询问,x<X,y<Y的修改。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=2000005;
int n,m,cnt,ma,ans[N],s[N];
struct pigu
{
    int pan,x,y,zai,xp,jia;
    friend bool operator <(pigu x,pigu y)
    {
        return x.x==y.x?(x.y==y.y?x.pan<y.pan:x.y<y.y):x.x<y.x;
    }
}a[N],hu[N];
inline bool cmp(pigu x,pigu y)
{
    return x.x==y.x?(x.y==y.y?x.pan<y.pan:x.y<y.y):x.x<y.x;
}
inline int lowbit(int x)
{
    return x&(-x);
}
inline void add(int x,int ad)
{
    for(int i=x;i<=ma;i+=lowbit(i)) s[i]+=ad;
}
inline int query(int x)
{
    int daan=0;
    while(x)
    {
        daan+=s[x];
        x-=lowbit(x);
    }
    return daan;
}
inline int read()
{
    char c=getchar();
    int x=0,f=1;
    while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)) {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*f;
}
inline void solve(int l,int r)
{
    if(l==r) return;
    int mid=(l+r)>>1;
    solve(l,mid);solve(mid+1,r);
    int o=l,ll=l,mm=mid+1;
    while(ll<=mid&&mm<=r)
    {
        if(a[ll]<a[mm])
        {
            if(a[ll].pan==1) add(a[ll].y,a[ll].jia); 
            hu[o++]=a[ll++];
        }
        else
        {    
            if(a[mm].pan==2) ans[a[mm].zai]+=a[mm].xp*query(a[mm].y);
            hu[o++]=a[mm++];
        }
    }
    while(ll<=mid) add(a[ll].y,a[ll].jia),hu[o++]=a[ll++];
    while(mm<=r)
    {
        if(a[mm].pan==2) ans[a[mm].zai]+=a[mm].xp*query(a[mm].y);
        hu[o++]=a[mm++];
    }
    for(int i=l;i<=mid;i++) if(a[i].pan==1) add(a[i].y,-a[i].jia);
    for(int i=l;i<=r;i++) a[i]=hu[i];
}
int main()
{
    int xipan=read(),gu=0;
    while(xipan!=3)
    {
        int x,y,xx,yy;
        if(xipan==0) ma=read()+1;
        if(xipan==1)
        {
            a[++cnt].x=read()+1;a[cnt].pan=1;a[cnt].y=read()+1;a[cnt].jia=read();
        }
        if(xipan==2)
        {
            x=read()+1;y=read()+1;xx=read()+1;yy=read()+1;
            a[++cnt].pan=2;a[cnt].x=x-1,a[cnt].y=y-1;a[cnt].zai=++gu;a[cnt].xp=1;
            a[++cnt].pan=2;a[cnt].x=x-1;a[cnt].y=yy;a[cnt].zai=gu;a[cnt].xp=-1;
            a[++cnt].pan=2;a[cnt].x=xx;a[cnt].y=y-1;a[cnt].zai=gu;a[cnt].xp=-1;
            a[++cnt].pan=2;a[cnt].x=xx;a[cnt].y=yy;a[cnt].zai=gu;a[cnt].xp=1;
        }
        if(xipan==3) break;
        xipan=read();
    }
    solve(1,cnt);
    for(int i=1;i<=gu;i++) cout<<ans[i]<<"
";
}
View Code

 

5.P4027 [NOI2007]货币兑换

首先斜率优化那个样子搞一哈虱子,然后发现x不递增,斜率不递增,就可以用splay动态维护凸包,二分查找最近的斜率。

还可以cdq分治一波,先总体全部按照k排序,左区间搞一手斜率单调递减,右区间由于k拍好了序,就可以直接在左边维护好的东西头查询。

注意每次操作前要先把整个区间按照时间排序拍好,递归完了就可以不需要管时间了。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const double eps=1e-7;
const double inf=1e10;
int n;
double f[N];
struct pigu
{
    double x,y,a,b,r,k;
    int id;
}wen[N],hu[N],hu2[N];
inline int read()
{
    char c=getchar();
    int x=0,f=1;
    while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)) {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*f;
}
inline bool cmp(pigu x,pigu y)
{
    return x.k<y.k;
}
inline double xl(int x,int y)
{
    if(fabs(wen[x].x-wen[y].x)<=eps) return inf;
    return (wen[x].y-wen[y].y)/(wen[x].x-wen[y].x);
}
int st[N];
inline void merge(int l,int r)
{
    int mid=(l+r)>>1;
    int t1=l,t2=mid+1;
    for(int i=l;i<=r;++i)
        if(t1<=mid&&(t2>r||wen[t1].x<wen[t2].x+eps)) hu[i]=wen[t1++];
        else hu[i]=wen[t2++];
    for(int i=l;i<=r;++i) wen[i]=hu[i];
}
inline void solve(int l,int r)
{
    if(l==r)
    {
        f[l]=max(f[l],f[l-1]);
        wen[l].y=f[l]/(wen[l].a*wen[l].r+wen[l].b);
        wen[l].x=wen[l].y*wen[l].r;
        return;
    }
    int mid=(l+r)>>1,ll=l-1,mm=mid;
    for(int i=l;i<=r;i++)
    {
        if(wen[i].id<=mid) hu[++ll]=wen[i];
        else hu[++mm]=wen[i];    
    }
    for(int i=l;i<=r;i++) wen[i]=hu[i];
    solve(l,mid);
    int top=0;
    for(int i=l;i<=mid;i++)
    {
        while(top>=2&&xl(st[top],st[top-1])<xl(i,st[top])+eps) top--;
        st[++top]=i;
    }
    for(int i=mid+1;i<=r;i++)
    {
        while(top>1&&xl(st[top],st[top-1])<wen[i].k+eps) top--;
        f[wen[i].id]=max(f[wen[i].id],wen[i].a*wen[st[top]].x+wen[i].b*wen[st[top]].y);
    }
    solve(mid+1,r);merge(l,r);
}
int main()
{
    n=read();scanf("%lf",&f[0]);
    for(int i=1;i<=n;i++)
    {
        scanf("%lf%lf%lf",&wen[i].a,&wen[i].b,&wen[i].r);
        wen[i].k=-wen[i].a/wen[i].b;wen[i].id=i;
    }
    sort(wen+1,wen+n+1,cmp);
    solve(1,n);
    printf("%.3lf",f[n]);
}
View Code

 

原文地址:https://www.cnblogs.com/betablewaloot/p/12489901.html