CDQ分治与整体二分总结

Cdq分治和整体二分是两个很奇妙的东西。他们都是通过离线的思想来进行优化,从而更快的求出解。

  整体二分通俗的讲就是二分答案,但是它了不起的地方是一下子把所有的答案都二分出来了,从而可以一下子得出所有查询。

  CDQ分治通俗的讲就是二分查询。通常的做法是把所有的查询分成两半,然后通过递归先计算出左边一半的所有的查询,然后通过这些已知的左半边的值来更新右半边的值。这里,最最重要的思想是通过左半边来更新右半边。更具体一点,就是用左半边的修改来更新右半边的查询。

重要的事情说话三遍:

  CDQ分治就是通过左半边的修改来更新右半边的查询

  CDQ分治就是通过左半边的修改来更新右半边的查询!

  CDQ分治就是通过左半边的修改来更新右半边的查询

CDQ的主要作用就是降维。因为当你二分查询的时候,你可以保证左半边的查询都满足你二分前的顺序。

一、整体二分

1. 最经典的题目:带修改的区间第k小数(hdu5412

因为相同的题目太多了,挑了一道比较新的题。其实就是题面不一样。

题目意思:

给你N个数,有两种询问。一个是修改第K个数的值,二是询问一个区间内的第k小的数,输出这个数。

所以,具体的做法就是二分答案。如果比猜测的数字小的个数比较多,那么答案一定在左边的区间内,反之就在右边的区间内。然后递归来继续这个操作。

   这是写的第一道整体二分,不要问我为什么和网上的代码几乎一模一样。我只是因为看了很久才看懂,所以几乎就背出来了……

代码:

#include <iostream>
#include <sstream>
#include <ios>
#include <iomanip>
#include <functional>
#include <algorithm>
#include <vector>
#include <string>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <climits>
#include <cctype>
#define INF 0x3f3f3f3f
#define MP(X,Y) make_pair(X,Y)
#define PB(X) push_back(X)
#define REP(X,N) for(int X=0;X<N;X++)
#define REP2(X,L,R) for(int X=L;X<=R;X++)
#define DEP(X,R,L) for(int X=R;X>=L;X--)
#define CLR(A,X) memset(A,X,sizeof(A))
#define IT iterator
#define M_PI 3.14159265358979323846
#define _ ios_base::sync_with_stdio(0);cin.tie(0);
#define X first
#define Y second
#define MAX_V 10101
#define maxn 323400
#define lowbit(X) (X & (-X))
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
typedef pair<PII,int>PPI;

int dat[maxn];
void add(int k,int val){
    while(k<maxn){
        dat[k]+=val;
        k+=lowbit(k);
    }
}
int query(int k){
    int sum=0;
    while(k>0){
        sum+=dat[k];
        k-=lowbit(k);
    }
    return sum;
}

struct {
    //sign x query val k
    int s,x,val,q,k,l,r,cur;
}q[maxn],q1[maxn],q2[maxn];
int A[maxn];
int temp[maxn];
int ans[maxn];
//×ó±ÕÓÒ±Õ
void div_solve(int head,int tail,int l,int r){
    //cout<<head<<‘ ‘<<tail<<‘ ‘<<l<<‘ ‘<<r<<endl;
    if(head>tail)return ;
    if(l==r){
        for(int i=head;i<=tail;++i){
            if(q[i].s==3)
                ans[q[i].q]=l;
        }
        return ;
    }
    int mid=l+r>>1;
    for(int i=head;i<=tail;++i){
        if(q[i].s==1 && q[i].val<=mid) add(q[i].x,1);
        else if(q[i].s==2 && q[i].val<=mid) add(q[i].x,-1);
        else if(q[i].s==3) temp[i]=query(q[i].r)-query(q[i].l-1);
    }
    for(int i=head;i<=tail;++i){
        if(q[i].s==1 && q[i].val<=mid) add(q[i].x,-1);
        else if(q[i].s==2 && q[i].val<=mid) add(q[i].x,1);
    }
    int l1=0,l2=0;
    for(int i=head;i<=tail;++i){
        if(q[i].s==3){
            if(q[i].cur+temp[i]>=q[i].k){
                q1[l1++]=q[i];
            }
            else{
                q[i].cur+=temp[i];
                q2[l2++]=q[i];
            }
        }
        else
        {
            if(q[i].val<=mid){
                q1[l1++]=q[i];
            }
            else{
                q2[l2++]=q[i];
            }
        }
    }
    for(int i=0;i<l1;++i){
        q[i+head]=q1[i];
    }
    for(int i=0;i<l2;++i){
        q[i+head+l1]=q2[i];
    }
    div_solve(head,head+l1-1,l,mid);
    div_solve(head+l1,tail,mid+1,r);
}

int main()
{
    int n;
    while(~scanf("%d",&n)){
        int a,b,c,d;
        int cnt=0;
        for(int i=1;i<=n;++i){
            scanf("%d",&a);
            A[i]=a;
            q[cnt].s=1;q[cnt].x=i;
            q[cnt].cur=0;
            q[cnt++].val=a;
        }
        int t,tt=0;
        scanf("%d",&t);
        for(int i=1;i<=t;++i){
            scanf("%d",&a);
            if(a==1){
                scanf("%d%d",&b,&c);
                q[cnt].s=2;q[cnt].x=b;q[cnt].cur=0;
                q[cnt++].val=A[b];
                q[cnt].s=1;q[cnt].x=b;q[cnt].cur=0;
                q[cnt++].val=c;
                A[b]=c;
            }
            else{
                scanf("%d%d%d",&a,&b,&c);
                q[cnt].s=3;q[cnt].l=a;q[cnt].q=++tt;
                q[cnt].cur=0;
                q[cnt].r=b;q[cnt++].k=c;
            }
        }
        div_solve(0,cnt-1,0,INF);
        for(int i=1;i<=tt;++i)
            printf("%d\n",ans[i]);
    }
    return 0;
}

2.POI 2011 Meteors

//因为bzoj现在都要收费了,找了半天终于找到免费的。

http://main.edu.pl/en/archive/oi/18/met

题目意思:

一个行星有一条圆形的轨道。这条轨道被分成了m份,这些轨道分别属于n个国家。一个国家可以拥有多个轨道。在轨道周围会有流星雨落下,现在各个国家需要收集一些流星雨。问你需要经过多少时间,各个国家才能完成他们的收集任务。

因为问的是时间,所以第一个想到是二分时间。接下来在维护一个线段树,用来区间修改。然后再根据二分时间,检查是否满足要求。继续二分,直到找到解。其中的一个处理是在t+1的时间再加一个无限大的流星,用来确定是不是能够成功收集。

代码:

#include <iostream>
#include <sstream>
#include <ios>
#include <iomanip>
#include <functional>
#include <algorithm>
#include <vector>
#include <string>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <climits>
#include <bitset>
#include <cctype>
#define INF 0x3f3f3f3f
#define MP(X,Y) make_pair(X,Y)
#define PB(X) push_back(X)
#define REP(X,N) for(int X=0;X<N;X++)
#define REP2(X,L,R) for(int X=L;X<=R;X++)
#define DEP(X,R,L) for(int X=R;X>=L;X--)
#define CLR(A,X) memset(A,X,sizeof(A))
#define IT iterator
#define M_PI 3.14159265358979323846
#define _ ios_base::sync_with_stdio(0);cin.tie(0);
#define X first
#define Y second
#define MAX_V 10101
#define maxn 301234
#define lowbit(X) (X & (-X))
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
typedef pair<PII,int>PPI;
const ll INF2=(0x3f3f3f3f)*100ll;

//bit Çø¼äÐÞ¸Ä µ¥µã²éѯ
ll dat[maxn];
//×ó±ÕÓÒ¿ª

void init(){
    memset(dat,0,sizeof(dat));
}
void add(int l,int r,int val){
    while(l<maxn){
        dat[l]+=val;
        l+=lowbit(l);
    }
    while(r<maxn){
        dat[r]-=val;
        r+=lowbit(r);
    }
}
ll query(int k){
    ll sum=0;
    while(k>0){
        sum+=dat[k];
        k-=lowbit(k);
    }
    return sum;
}

struct edge{
    int to,next;
}G[maxn];
int head[maxn];
void add_edge(int from,int to,int &cnt){
    edge e;
    e.to=to;e.next=head[from];
    G[++cnt]=e;
    head[from]=cnt;
}

struct node{
    int l,r,num;
}Q[maxn];
ll cur[maxn];
int A[maxn],ID[maxn],T[maxn],Ans[maxn];
int n,m,t;
int temp[maxn],temp2[maxn];

void solve(int start,int tail,int l,int r){
    if(start>tail) return ;
    if(l==r){
        for(int i=start;i<=tail;++i){
            int d=ID[i];
            Ans[d]=l;
        }
        return ;
    }
    //init();
    int mid=(l*1ll+r)>>1;
    for(int i=l;i<=mid;++i){
        int l=Q[i].l,r=Q[i].r,num=Q[i].num;
        if(l>r){
            add(l,m+1,num);
            add(1,r+1,num);
        }
        else
            add(l,r+1,num);
    }
    int l1=0,l2=0;
    for(int i=start;i<=tail;++i){
        int d=ID[i];
        ll cnt=0;
        for(int j=head[d];j;j=G[j].next){
            int to=G[j].to;
            cnt+=query(to);
            if(cnt>INF)break;
        }
        if(cnt+cur[d]>=T[d]){
            temp[l1++]=d;
        }
        else{
            temp2[l2++]=d;
            cur[d]+=cnt;
        }
    }
    for(int i=0;i<l1;++i){
        ID[start+i]=temp[i];
    }
    for(int i=0;i<l2;++i){
        ID[start+l1+i]=temp2[i];
    }
    for(int i=l;i<=mid;++i){
        int l=Q[i].l,r=Q[i].r,num=Q[i].num;
        if(l>r){
            add(l,m+1,-num);
            add(1,r+1,-num);
        }
        else
            add(l,r+1,-num);
    }
    //cout<<l<<" "<<r<<endl;
    solve(start,start+l1-1,l,mid);
    solve(start+l1,tail,mid+1,r);
}

int Scan(){
    int res=0, ch;
    ch=getchar();
    //windows»Ø³µ\r\n  linux»Ø³µ\n  ·ÀÖ¹Êý¾ÝÖÐûÓÐÈ¥³ý\r
    if(ch==10) ch=getchar();
    if(ch>=‘0‘&&ch<=‘9‘)
        res=ch-‘0‘;
    else return -1;
    while((ch=getchar())>=‘0‘&&ch<=‘9‘)
        res=res*10+ch-‘0‘;
    return res;
}
void Out(ll a){
    if(a>9)
        Out(a/10);
    putchar(a%10+‘0‘);
}
int main()
{
    //scanf("%d%d",&n,&m);
    n=Scan();m=Scan();
    memset(head,0,sizeof(head));
    int tot=0;
    for(int i=1;i<=m;++i){
        //scanf("%d",&A[i]);
        A[i]=Scan();
        add_edge(A[i],i,tot);
    }
    for(int i=1;i<=n;++i){
        //scanf("%d",&T[i]);
        T[i]=Scan();
        ID[i]=i;
    }
    //scanf("%d",&t);
    t=Scan();
    int l,r,num;
    for(int i=1;i<=t;++i){
        //scanf("%d%d%d",&l,&r,&num);
        l=Scan();r=Scan();num=Scan();
        Q[i].l=l;Q[i].r=r;
        Q[i].num=num;
    }
    t+=1;
    Q[t].num=INF;Q[t].l=1;Q[t].r=m;
    //memset(cur,0,sizeof(cur));
    solve(1,n,1,t);
    for(int i=1;i<=n;++i){
        if(Ans[i]==t){
            puts("NIE");
        }
        else{
            //printf("%d\n",Ans[i]);
            Out(Ans[i]);
            putchar(‘\n‘);
        }
    }

    return 0;
}

其他学习资料:http://www.cnblogs.com/zig-zag/archive/2013/04/18/3027707.html

二、CDQ分治

1.Boring Class(hdu5324)

题目意思:给你两个序列,分别为L序列和R序列。求一个最长的序列,满足对于每个Vi,Vj,L[Vi]>=L[Vj] 并且R[Vi]<=R[Vj],同时还要的是字典序最小这个条件。

这题是典型的CDQ分治的题目。因为有三维,顺序维、L维和R维,我们不好处理。所以这个时候就可以通过CDQ分治来二分它的顺序。这样就可以保证在CDQ的过程中,时间顺序一定是满足的。接下来在CDQ中,我们不妨按照R来排序,这样可以确定R也满足顺序,然后再进行DP就可以得出结果了。其中要注意的是从右往左DP,这样可以求出字典序的最小。

代码:

#include <iostream>
#include <sstream>
#include <ios>
#include <iomanip>
#include <functional>
#include <algorithm>
#include <vector>
#include <string>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <climits>
#include <bitset>
#include<cassert>
#include <cctype>
#define NINF -0x3f3f3f3f
#define MP(X,Y) make_pair(X,Y)
#define PB(X) push_back(X)
#define REP(X,N) for(int X=0;X<N;X++)
#define REP2(X,L,R) for(int X=L;X<=R;X++)
#define DEP(X,R,L) for(int X=R;X>=L;X--)
#define CLR(A,X) memset(A,X,sizeof(A))
#define IT iterator
#define M_PI 3.14159265358979323846
#define _ ios_base::sync_with_stdio(0);cin.tie(0);
#define X first
#define Y second
#define MAX_V 10101
#define maxn 50099
#define lowbit(X) (X & (-X))
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
typedef pair<PII,int>PPI;

int tot;
int L[maxn],R[maxn];
int Num[maxn*2],D[maxn];
struct node{
    int l,r,id;
    bool operator<(const node &B)const{
        if(r!=B.r)return r<B.r;
        if(l!=B.l)return l>B.l;
        return id<B.id;
    }
}q1[maxn],q2[maxn];
int dat[maxn*2];
void update(int k,int val){
    while(k<tot+10){
        dat[k]=max(dat[k],val);
        k+=lowbit(k);
    }
}
void clear(int k){
    while(k<tot+10){
        dat[k]=0;
        k+=lowbit(k);
    }
}
int query(int k){
    int Max=0;
    while(k>0){
        Max=max(Max,dat[k]);
        k-=lowbit(k);
    }
    return Max;
}

void cdq(int l,int r){
    if(l==r)return;
    int mid=(l+r)/2;
    cdq(mid+1,r);
    for(int i=l;i<=mid;++i){
        q1[i].l=L[i];q1[i].r=R[i];q1[i].id=i;
    }
    for(int i=mid+1;i<=r;++i){
        q2[i].l=L[i];q2[i].r=R[i];q2[i].id=i;
    }
    sort(q1+l,q1+mid+1);
    sort(q2+mid+1,q2+r+1);
    for(int i=mid,j=r;i>=l;--i){
        while(j>mid && q2[j].r>=q1[i].r){
            update(q2[j].l,D[q2[j].id]);
            j--;
        }
        D[q1[i].id]=max(D[q1[i].id],query(q1[i].l)+1);
    }
    for(int i=mid+1;i<=r;++i){
        clear(q2[i].l);
    }
    cdq(l,mid);
}

int Scan(){
    int res=0, ch;
    ch=getchar();
    //windows»Ø³µ\r\n  linux»Ø³µ\n  ·ÀÖ¹Êý¾ÝÖÐûÓÐÈ¥³ý\r
    if(ch==10) ch=getchar();
    if(ch>=‘0‘&&ch<=‘9‘)
        res=ch-‘0‘;
    else return -1;
    while((ch=getchar())>=‘0‘&&ch<=‘9‘)
        res=res*10+ch-‘0‘;
    return res;
}
void Out(ll a){
    if(a>9)
        Out(a/10);
    putchar(a%10+‘0‘);
}

int main()
{
    int n;
    //~(n=Scan())
    //freopen("data.in","r",stdin);
    while(~scanf("%d",&n)){
        tot=0;
        for(int i=1;i<=n;++i){
            scanf("%d",&L[i]);
            //L[i]=Scan();
            Num[tot++]=L[i];
        }
        for(int i=1;i<=n;++i){
            scanf("%d",&R[i]);
            //R[i]=Scan();
            Num[tot++]=R[i];
        }
        sort(Num,Num+tot);
        tot=unique(Num,Num+tot)-Num;
        for(int i=1;i<=n;++i){
            L[i]=(lower_bound(Num,Num+tot,L[i])-Num)+1;
            R[i]=(lower_bound(Num,Num+tot,R[i])-Num)+1;
        }
    //    cout<<"ok"<<endl;
        for(int i=1;i<maxn;++i)
            D[i]=1;
        cdq(1,n);
        int ans=0;
        for(int i=1;i<=n;++i){
            ans=max(D[i],ans);
        }
        printf("%d\n",ans);
        //Out(ans);putchar(‘\n‘);
        int pre=0;
        for(int i=1;i<=n;++i){
            if(D[i]==ans && (pre==0 || ( L[pre]>=L[i] && R[pre]<=R[i]))){
                ans--;
                if(pre)putchar(‘ ‘);
                printf("%d",i);
                //Out(i);
                pre=i;
            }
        }
        putchar(‘\n‘);
    }
    return 0;
}

2. Pinball Game 3Dhdu4742

题目意思:给你三个序列X,Y,Z求最长上升序列的长度。同时输出可以构成最长上升序列的方法数。

和之前的一题几乎一样,多了一个维护方法数。所以树状数组不仅要维护一个Dp的值,同时还要维护一个方法数。如果Dp更新了,方法数就是更新它的值,反之,在原来的基础上要加上那种方法的个数。

代码:

#include <iostream>
#include <sstream>
#include <ios>
#include <iomanip>
#include <functional>
#include <algorithm>
#include <vector>
#include <string>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <climits>
#include <bitset>
#include <cctype>
#define NINF -0x3f3f3f3f
#define MP(X,Y) make_pair(X,Y)
#define PB(X) push_back(X)
#define REP(X,N) for(int X=0;X<N;X++)
#define REP2(X,L,R) for(int X=L;X<=R;X++)
#define DEP(X,R,L) for(int X=R;X>=L;X--)
#define CLR(A,X) memset(A,X,sizeof(A))
#define IT iterator
#define M_PI 3.14159265358979323846
#define _ ios_base::sync_with_stdio(0);cin.tie(0);
#define X first
#define Y second
#define MAX_V 10101
#define maxn 100009
#define lowbit(X) (X & (-X))
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,ll>PII;
typedef pair<PII,int>PPI;

int Fu[maxn*3];
ll Num[maxn];
int D[maxn];
const ll MOD=(1<<30);
struct node{
    int x,y,z;
    bool operator <(const node&B) const{
        if(x!=B.x) return x<B.x;
        if(y!=B.y) return y<B.y;
        return z<B.z;
    }
}A[maxn];
struct node2{
    int x,y,z,id;
    bool operator<(const node2&B)const{
        if(y!=B.y) return y<B.y;
        if(z!=B.z) return z<B.z;
        return id<B.id;
    }
}q1[maxn],q2[maxn];
PII dat[maxn*3];
void update(int k,int val,ll num){
    while(k<maxn*3){
        if(val>dat[k].X){
            dat[k].X=val;
            dat[k].Y=num;
        }
        else if(val==dat[k].X){
            dat[k].Y+=num;
            dat[k].Y%=MOD;
        }
        k+=lowbit(k);
    }
}
PII  query(int k){
    PII Max=MP(0,0);
    while(k>0){
        if(dat[k].X>Max.X){
            Max=dat[k];
        }
        else if(dat[k].X==Max.X){
            Max.Y+=dat[k].Y;
            Max.Y%=MOD;
        }
        k-=lowbit(k);
    }
    return Max;
}
void clear(int k){
    while(k<maxn*3){
        dat[k]=MP(0,0);
        k+=lowbit(k);
    }
}
void cdq(int l,int r){
    if(l==r)return ;
    int mid=l+r>>1;
    cdq(l,mid);
    for(int i=l;i<=mid;++i){
        q1[i].x=A[i].x;q1[i].y=A[i].y;
        q1[i].z=A[i].z;q1[i].id=i;
    }
    for(int i=mid+1;i<=r;++i){
        q2[i].x=A[i].x;q2[i].y=A[i].y;
        q2[i].z=A[i].z;q2[i].id=i;
    }
    sort(q1+l,q1+mid+1);
    sort(q2+mid+1,q2+r+1);
    for(int i=mid+1,j=l;i<=r;++i){
        while(j<=mid && q1[j].y<=q2[i].y){
            update(q1[j].z,D[q1[j].id],Num[q1[j].id]);
            j++;
        }
        PII temp=query(q2[i].z);
        if(D[q2[i].id]<temp.X+1){
            D[q2[i].id]=temp.X+1;
            Num[q2[i].id]=temp.Y;
        }
        else if(D[q2[i].id]==temp.X+1){
            Num[q2[i].id]+=temp.Y;
            Num[q2[i].id]%=MOD;
        }
    }
    for(int i=l;i<=mid;++i){
        clear(q1[i].z);
    }
    cdq(mid+1,r);
}

int Scan(){
    int res=0, ch;
    ch=getchar();
    //windows»Ø³µ\r\n  linux»Ø³µ\n  ·ÀÖ¹Êý¾ÝÖÐûÓÐÈ¥³ý\r
    if(ch==10) ch=getchar();
    if(ch>=‘0‘&&ch<=‘9‘)
        res=ch-‘0‘;
    else return -1;
    while((ch=getchar())>=‘0‘&&ch<=‘9‘)
        res=res*10+ch-‘0‘;
    return res;
}
void Out(ll a){
    if(a>9)
        Out(a/10);
    putchar(a%10+‘0‘);
}
int main()
{
    int T;
    //scanf("%d",&T);
    T=Scan();
    while(T--){
        int n;
        //scanf("%d",&n);
        n=Scan();
        int tot=0;
        for(int i=1;i<=n;++i){
            //scanf("%d%d%d",&A[i].x,&A[i].y,&A[i].z);
            A[i].x=Scan();A[i].y=Scan();A[i].z=Scan();
            Fu[tot++]=A[i].x;
            Fu[tot++]=A[i].y;
            Fu[tot++]=A[i].z;
        }
        sort(Fu,Fu+tot);
        for(int i=1;i<=n;++i){
            D[i]=Num[i]=1;
            A[i].x=lower_bound(Fu,Fu+tot,A[i].x)-Fu+1;
            A[i].y=lower_bound(Fu,Fu+tot,A[i].y)-Fu+1;
            A[i].z=lower_bound(Fu,Fu+tot,A[i].z)-Fu+1;
        }
        sort(A+1,A+n+1);
        cdq(1,n);
        int Max=0;
        for(int i=1;i<=n;++i){
            Max=max(Max,D[i]);
        }
        ll sum=0;
        for(int i=1;i<=n;++i){
            if(D[i]==Max){
                sum=sum+Num[i];
                sum%=MOD;
            }
        }
        printf("%d %I64d\n",Max,sum);
    }
    return 0;
}

3. Machine Works(hdu3842)

这是2011WorldFinal的一道题目,所以做的人不多。要是我当时看到是WorldFinal的题,肯定吓得直接跳过了。但是事实上难度还可以。

题目意思:

初始时,给你一笔资金C元,以及一段时间D天。在其中的某天会提供给你购买机器的机会,你可以选择购买或者不购买。买机器需要花费P元,卖出可以获得R元,从够买机器的第二天起,到卖出机器的前一天,你可以每天获得G元。最后一天将卖出所有机器。同时,由于场地限制,最多只能同时拥有一台机器。

首先我们可以用D[i]来维护前一个状态结束,刚刚到达i这个机器时,所能拥有的最大值。

D[i]=max{D[j]-P[j]+R[j]+G[j]*(day[i]-day[j]-1)}

         因为j的信息都是已知的,所以可以化解为

            D[i]=C+k*day[i];

所以也就化解为斜率优化DP的问题了。

所以我们接下来按照斜率从小到大来排序,用一个队列来维护。队列中的元素满足k是递增的。所以队尾的元素一定在某些x时候会比队尾的前一个元素大。现在当你要新加入一个元素到末尾的时候,新加入的元素可能无论在什么时候都比原先在队尾的元素大,这样就删掉那个元素。

同时对于队首的维护,当目前的x不是最大时,删除这个队首的值。DP一下就可以了。

代码:

#include <iostream>
#include <sstream>
#include <ios>
#include <iomanip>
#include <functional>
#include <algorithm>
#include <vector>
#include <string>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <climits>
#include <bitset>
#include <cctype>
#define NINF -0x3f3f3f3f
#define MP(X,Y) make_pair(X,Y)
#define PB(X) push_back(X)
#define REP(X,N) for(int X=0;X<N;X++)
#define REP2(X,L,R) for(int X=L;X<=R;X++)
#define DEP(X,R,L) for(int X=R;X>=L;X--)
#define CLR(A,X) memset(A,X,sizeof(A))
#define IT iterator
#define M_PI 3.14159265358979323846
#define _ ios_base::sync_with_stdio(0);cin.tie(0);
#define X first
#define Y second
#define MAX_V 10101
#define maxn 123456
#define lowbit(X) (X & (-X))
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
typedef pair<PII,int>PPI;
//19:52

ll D[maxn];
struct node{
    int day,pri,ret,val,id;
    bool operator <(const node &B)const {
        return day<B.day;
    }
}A[maxn];
struct node2{
    int day,pri,ret,val,id;
    bool operator <(const node2 &B)const {
        if(val==B.val){
            ll t1=D[id]+ret-pri-1ll*(day+1)*val;
            ll t2=D[B.id]+B.ret-B.pri-1ll*(B.day+1)*B.val;
            return t1<t2;
        }
        return val<B.val;
    }
}q1[maxn],q2[maxn];
ll K[maxn],B[maxn];
ll f(int i,int x){
    ll ans=K[i]*x+B[i];
    return ans;
}
bool check(int i1,int i2,ll k3,ll b3){
    ll k1=K[i1],b1=B[i1],k2=K[i2],b2=B[i2];
    return 1.0*(k3-k2)*(b1-b3)-1.0*(b2-b3)*(k3-k1)<0;
    //return (double)(k2-k1)*(b3-b1)-(double)(k3-k1)*(b2-b1)<0;
}
void cdq(int l,int r){
    if(l==r)return ;
    int mid=l+r>>1;
    cdq(l,mid);
    for(int i=l;i<=mid;++i){
        q1[i].day=A[i].day;q1[i].pri=A[i].pri;
        q1[i].ret=A[i].ret;q1[i].val=A[i].val;
        q1[i].id=A[i].id;
    }
    for(int i=mid+1;i<=r;++i){
        q2[i].day=A[i].day;q2[i].pri=A[i].pri;
        q2[i].ret=A[i].ret;q2[i].val=A[i].val;
        q2[i].id=A[i].id;
    }
    sort(q1+l,q1+mid+1);
    int head=0,tail=-1;
    for(int i=l;i<=mid;++i){
        if(D[q1[i].id]<q1[i].pri)continue;
        ll t1=q1[i].val;
        ll t2=D[q1[i].id]+q1[i].ret-q1[i].pri-1ll*(q1[i].day+1)*q1[i].val;

//        if(tail-head+1<2){
//            K[++tail]=t1;
//            B[tail]=t2;
//            //if(head-tail==1 && K[head]==K[tail] && B[head]<=B[tail])head++;
//        }
//        else{

            while(tail>head && !check(tail-1,tail,t1,t2))tail--;
            K[++tail]=t1;
            B[tail]=t2;
        //}
    }
    for(int i=mid+1;i<=r;++i){
        int d=q2[i].day;
        while(head<tail && f(head,d)<=f(head+1,d))head++;
        ll temp=f(head,d);
        if(temp<0)continue;
        D[q2[i].id]=max(temp,D[q2[i].id]);
    }
    cdq(mid+1,r);
}

int main()
{
    int n,c,d;
    int tt=0;
    while(~scanf("%d%d%d",&n,&c,&d)){
        if(n==0 && c==0 && d==0)break;
        D[0]=c;
        A[0].id=0;A[0].day=0;A[0].val=0;A[0].pri=0;A[0].ret=0;
        for(int i=1;i<=n;++i){
            scanf("%d%d%d%d",&A[i].day,&A[i].pri,&A[i].ret,&A[i].val);
            A[i].id=i;
            D[i]=c;
        }
        A[n+1].day=d+1;A[n+1].id=n+1;
        A[n+1].pri=A[n+1].val=A[n+1].ret=0;
        D[n+1]=c;
        sort(A+1,A+n+1);
        cdq(0,n+1);
        printf("Case %d: %I64d\n",++tt,D[n+1]);
    }
    return 0;
}

4. 动态逆序对(BZOJ3295)

题目意思:给你一个序列求每次删除一个元素之前的逆序对的个数。

假设你要删除一个元素,那么减少的逆序对的个数不仅和他前面位置的有关,还和他后面位置的数有关。所以假设已经知道在最初状态时在他前面和后面分别的逆序对数。在删除的时候,更新这两个值,问题就解决了。

至于逆序对的个数,预处理一下就可以了。

   代码:

#include <iostream>
#include <sstream>
#include <ios>
#include <iomanip>
#include <functional>
#include <algorithm>
#include <vector>
#include <string>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <climits>
#include <bitset>
#include <cctype>
#define NINF -0x3f3f3f3f
#define MP(X,Y) make_pair(X,Y)
#define PB(X) push_back(X)
#define REP(X,N) for(int X=0;X<N;X++)
#define REP2(X,L,R) for(int X=L;X<=R;X++)
#define DEP(X,R,L) for(int X=R;X>=L;X--)
#define CLR(A,X) memset(A,X,sizeof(A))
#define IT iterator
#define M_PI 3.14159265358979323846
#define _ ios_base::sync_with_stdio(0);cin.tie(0);
#define X first
#define Y second
#define MAX_V 10101
#define maxn 123456
#define lowbit(X) (X & (-X))
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
typedef pair<PII,int>PPI;

ll dat[maxn];
void update(int k,int val){
    while(k<maxn){
        dat[k]+=val;
        k+=lowbit(k);
    }
}
ll query(int k){
    ll sum=0;
    while(k>0){
        sum+=dat[k];
        k-=lowbit(k);
    }
    return sum;
}
int A[maxn];
int Pos[maxn];
ll L[maxn],R[maxn];
struct node{
    int val,id;
    bool operator <(const node &B)const {
        return Pos[val]<Pos[B.val];
    }
}Q[maxn],q1[maxn],q2[maxn];
ll Ans[maxn];
int n,m;
void solve(int l,int r){
    if(l==r){
    //    cout<<Pos[Q[l].val]<<" "<<L[Pos[Q[l].val]]<<" "<<R[Pos[Q[l].val]]<<endl;
        Ans[l]=L[Pos[Q[l].val]]+R[Pos[Q[l].val]];
        return ;
    }
    int mid=l+r>>1;
    solve(l,mid);
    int l1=0,l2=0;
    for(int i=l;i<=mid;++i){
        q1[l1++]=Q[i];
    }
    for(int i=mid+1;i<=r;++i){
        q2[l2++]=Q[i];
    }
    sort(q1,q1+l1);
    sort(q2,q2+l2);
    int j=0;
    for(int i=0;i<l2;++i){
        while(j<l1 && Pos[q1[j].val]<Pos[q2[i].val]){
            update(q1[j].val,1);
            //cout<<" "<<Pos[q1[j].val]<<" "<<n<<" "<<query(n)<<endl;
            j++;
        }
        int ps=Pos[q2[i].val];
        L[ps]-=query(n)-query(q2[i].val);
    }
    for(int i=0;i<j;++i){
        update(q1[i].val,-1);
    }
    //----
    j=l1-1;
    for(int i=l2-1;i>=0;--i){
        while(j>=0 && Pos[q1[j].val]>Pos[q2[i].val]){
            update(q1[j].val,1);
            j--;
        }
        int ps=Pos[q2[i].val];
        R[ps]-=query(q2[i].val);
    }
    for(int i=l1-1;i>j;--i){
        update(q1[i].val,-1);
    }
    solve(mid+1,r);
}

int main()
{
    while(~scanf("%d%d",&n,&m)){
        memset(dat,0,sizeof(dat));
        ll sum=0;
        for(int i=1;i<=n;++i){
            scanf("%d",&A[i]);
            L[i]=query(n)-query(A[i]);
            sum+=L[i];
            update(A[i],1);
            Pos[A[i]]=i;
        }
        memset(dat,0,sizeof(dat));
        for(int i=n;i>=1;--i){
            R[i]=query(A[i]);
            update(A[i],1);
        }
        int a;
        for(int i=1;i<=m;++i){
            scanf("%d",&a);
            Q[i].val=a;
            Q[i].id=i;
        }
        memset(dat,0,sizeof(dat));
        solve(1,m);
        for(int i=1;i<=m;++i){
            printf("%lld\n",sum);
            sum-=Ans[i];
        }
    }
    return 0;
}

5. Crowd(hdu4456)

题目意思:给你一张图,有两种查询,1.是给某个点的值增加Z  2.是查询曼哈顿距离小于某个点的值的和。

首先,需要的是坐标轴旋转。然后就可以用CDQ分治,或者是二维树状数组来搞了。

这篇讲的很好,我就不废话了。

     http://www.aiuxian.com/article/p-2286542.html

代码:树状数组

#include <iostream>
#include <sstream>
#include <ios>
#include <iomanip>
#include <functional>
#include <algorithm>
#include <vector>
#include <string>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <climits>
#include <bitset>
#include <cctype>
#define NINF -0x3f3f3f3f
#define MP(X,Y) make_pair(X,Y)
#define PB(X) push_back(X)
#define REP(X,N) for(int X=0;X<N;X++)
#define REP2(X,L,R) for(int X=L;X<=R;X++)
#define DEP(X,R,L) for(int X=R;X>=L;X--)
#define CLR(A,X) memset(A,X,sizeof(A))
#define IT iterator
#define M_PI 3.14159265358979323846
#define _ ios_base::sync_with_stdio(0);cin.tie(0);
#define X first
#define Y second
#define MAX_V 10101
#define maxn 1
#define lowbit(X) (X & (-X))
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
typedef pair<PII,int>PPI;

void change(int &x,int &y,int n){
    int t=x-y+n;
    y=x+y-1;
    x=t;
}
const int MOD=3001003;
int dat[MOD];
int num[MOD];
int Hash(int x){
    int t=x%MOD;
    while(1){
        if(num[t]==x || num[t]==-1){
            num[t]=x;
            return t;
        }
        t++;
        if(t==MOD)t=0;
    }
    return -1;
}
int gethash(int x){
    int t=x%MOD;
    while(1){
        if(num[t]==-1 || num[t]==x)return t;
        t++;
        if(t==MOD)t=0;
    }
    return -1;
}
int Bound;
void add(int x,int y,int add){
    for(int i=x;i<Bound;i+=lowbit(i)){
        for(int j=y;j<Bound;j+=lowbit(j)){
            dat[Hash(i*Bound+j)]+=add;
        }
    }
}
int getsum(int x,int y){
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i)){
        for(int j=y;j>0;j-=lowbit(j)){
            ans+=dat[gethash(i*Bound+j)];
        }
    }
    return ans;
}
int query(int a,int b,int c,int d){
    int ans=0;
    a=max(a,1);b=max(b,1);
    c=min(Bound-1,c);
    d=min(Bound-1,d);
    ans=getsum(c,d)+getsum(a-1,b-1)
        -getsum(a-1,d)-getsum(c,b-1);
    return ans;
}
int main()
{
    int n,m;
    while(1){
        scanf("%d",&n);
        if(n==0)break;
        scanf("%d",&m);
        Bound=n*2+1;
        memset(dat,0,sizeof(dat));
        memset(num,-1,sizeof(num));
        int a,x,y,z;
        for(int i=0;i<m;++i){
            scanf("%d%d%d%d",&a,&x,&y,&z);
            change(x,y,n);
            if(a==1){
                add(x,y,z);
            }
            else{
                printf("%d\n",query(x-z,y-z,x+z,y+z));
            }
        }
    }
    return 0;
}

代码:CDQ

#include <iostream>
#include <sstream>
#include <ios>
#include <iomanip>
#include <functional>
#include <algorithm>
#include <vector>
#include <string>
#include <list>
#include <queue>
#include <deque>
#include <stack>
#include <set>
#include <map>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <climits>
#include <bitset>
#include <cctype>
#define NINF -0x3f3f3f3f
#define MP(X,Y) make_pair(X,Y)
#define PB(X) push_back(X)
#define REP(X,N) for(int X=0;X<N;X++)
#define REP2(X,L,R) for(int X=L;X<=R;X++)
#define DEP(X,R,L) for(int X=R;X>=L;X--)
#define CLR(A,X) memset(A,X,sizeof(A))
#define IT iterator
#define M_PI 3.14159265358979323846
#define _ ios_base::sync_with_stdio(0);cin.tie(0);
#define X first
#define Y second
#define MAX_V 9
#define maxn 81234
#define lowbit(X) (X & (-X))
#include<ctime>
using namespace std;
typedef long long ll;
typedef pair<int,int>PII;
typedef pair<PII,int>PPI;

void change(int &x,int &y,int n){
    int t=x-y+n;
    y=x+y-1;
    x=t;
}
int Bound;
struct node{
    int s,x,y,z,id;
    bool operator <(const node &B)const{
        if(x!=B.x)return x<B.x;
        return y<B.y;
    }
}Q[maxn*4],q1[maxn*4],q2[maxn*4];

int Ans[maxn];
void addnode(int s,int x,int y,int z,int id,int &tot){
    Q[++tot].s=s;
    Q[tot].x=x;Q[tot].y=y;Q[tot].z=z;
    Q[tot].id=id;
}
int dat[maxn];
void add(int k,int val){
    while(k<Bound){
        dat[k]+=val;
        k+=lowbit(k);
    }
}
int query(int k){
    int sum=0;
    while(k>0){
        sum+=dat[k];
        k-=lowbit(k);
    }
    return sum;
}
void cdq(int l,int r){
    if(l==r)return;
    int mid=l+r>>1;
    cdq(l,mid);
    int l1=0,l2=0;
    for(int i=l;i<=mid;++i){
        q1[l1++]=Q[i];
    }
    for(int i=mid+1;i<=r;++i){
        q2[l2++]=Q[i];
    }
    sort(q1,q1+l1);
    sort(q2,q2+l2);
    int j=0;
    for(int i=0;i<l2;++i){
        if(q2[i].s==1)continue;
        while(j<l1 &&(q1[j].s==2 || q1[j].x<=q2[i].x)){
            if(q1[j].s==1)add(q1[j].y,q1[j].z);
            j++;
        }
        Ans[q2[i].id]+=query(q2[i].y)*q2[i].z;
    }
    for(int i=j-1;i>=0;--i)
    if(q1[i].s==1)add(q1[i].y,-q1[i].z);
    cdq(mid+1,r);
}

int main()
{
    int n,m;
    while(1){
        scanf("%d",&n);
        if(n==0)break;
        scanf("%d",&m);
        Bound=n*2+1;
        int s,x,y,z;
        int a,b,c,d;
        int tot=0;
        for(int i=1;i<=m;++i){
            scanf("%d%d%d%d",&s,&x,&y,&z);
            change(x,y,n);
            if(s==1){
                addnode(s,x,y,z,i,tot);
            }
            else{
                a=x-z;b=y-z;c=x+z;d=y+z;
                a=max(a,1);b=max(b,1);
                c=min(Bound-1,c);
                d=min(Bound-1,d);
                addnode(s,c,d,1,i,tot);
                addnode(s,a-1,b-1,1,i,tot);
                addnode(s,c,b-1,-1,i,tot);
                addnode(s,a-1,d,-1,i,tot);
            }
        }
        memset(Ans,0,sizeof(Ans));
        cdq(1,tot);
        for(int i=1;i<=tot;++i){
            if(Q[i].s==2){
                printf("%d\n",Ans[Q[i].id]);
                i+=3;
            }
        }
    }
    return 0;
}

6.隐藏题目

这是一道高神出的题目。因为这题目前还未出现过,所以这部分题解不公开。

/*

(------这是一个坑------)

*/

时间: 2024-12-10 02:51:50

CDQ分治与整体二分总结的相关文章

【cdq分治】cdq分治与整体二分学习笔记Part1.整体二分

之所以把cdq分治和整体二分放在一起学习,是因为他们两个实在太像了-不管是做法还是代码- 感觉整体二分可能会比cdq分治稍微简单那么一点点?所以先学整体二分.(感觉他们的区别在于整体二分是对每个操作二分答案,cdq是分治了操作序列) 整体二分是对答案进行二分,其具体操作如下: (比如以ZJOJ2013K大数查询为例) 具体过程 Step1.从(L,R)二分答案.mid=(L+R)>>1,用线段树维护原序列中(a,b)位置比mid大的数有多少个,同时记录对序列的操作分别是什么操作. Step2.

CDQ分治与整体二分小结

前言 这是一波强行总结. 下面是一波瞎比比. 这几天做了几道CDQ/整体二分,感觉自己做题速度好慢啊. 很多很显然的东西都看不出来 分治分不出来 打不出来 调不对 上午下午晚上的效率完全不一样啊. 完蛋.jpg 绝望.jpg. 关于CDQ分治 CDQ分治,求的是三维偏序问题都知道的. 求法呢,就是在分治外面先把一维变成有序 然后分治下去,左边(l,mid)关于右边(mid+1,r)就不存在某一维的逆序了,所以只有两维偏序了. 这个时候来一波"树状数组求逆序对"的操作搞一下二维偏序 就可

CQH分治与整体二分

CDH分治,核心思想就是对操作进行二分.感觉和我以前对操作分块的思想很像啊,fhb分块 ……(⊙o⊙)… 日常懒得写模板的题解,转载一篇(本家) -----------------------------------------------------------分割线---------------------------------------------------------------------- 在线/离线:首要考虑 在线算法: 可以以序列化的方式一个一个的处理输入,不必事先知道所有

时间分治和整体二分总结

时间分治(又叫cdq分治),是解决一类“贡献独立”.“支持离线”的数据结构问题的算法. 假设有一个操作序列:ABAABAABBAAAB,其中每个A对其后面的B有一定贡献,要求输出每个B对应的答案. “贡献独立”是指:每个A对其后面的B的影响是不受其他A影响的,即是要我们用B前面的所有A更新过B,那么B的答案就是正确的. 贡献独立的例子:max,min,sum,count(极值,和,满足某种条件的A的个数). “支持离线”是指每个AB必须开始时就给出,有些问题(如维护凸壳优化DP),可能A和B是合

[整体二分]【学习笔记】【更新中】

先小结一下吧 主要为个人理解 整体二分 理解 $zyz:$整体二分是在权值上进行$CDQ$分治 我觉得更像是说$:$整体二分是在答案上进行$CDQ$分治 整体二分是二分答案在数据结构题上的扩展 因为数据结构题二分的答案通常是第几个操作之后,需要进行一些操作(预处理)之后才能判断,所以每次询问二分还不如从前往后暴力找 整体二分可以解决这样的问题 核心就是维护一个$cur$数组保存当前的影响,分治到$[l,r]$时只需要计算$[l,mid]$的影响再与$cur$里的合并就好了 这样分治里的操作就只与

4538: [Hnoi2016]网络 链剖 + 堆(优先队列) / 整体二分

GDOI之后写的第一道题.看到之后没什么感觉(是我太弱,中途一度想用kpm之前在某道题上用过的链表的方法.想了想应该不可能.) 好!让我们来分析这道题吧!首先简化模型,它是要求维护树上的一些路径,支持添加和修改,要求不经过某个点的路径的最大权值(不经过某个点,我一度想到了动点分,虽然我还不会). 我们可以先考虑在链上(其实仔细一想,如果链上的你会做,那么树上的大多数情况下便是多个了链剖而已吧!)的情况.在链上,有一些区间覆盖,要求没有覆盖某个点的区间的最大权值.那么我们接着想如果询问2询问了一个

K-th Number POJ - 2104 (整体二分)

K-th Number POJ - 2104 之前学主席树写了一遍 最近再看CDQ分治和整体二分,一直不是很理解,看着别人代码稍微理解了一些 1 //比主席树慢了挺多 2 #include <iostream> 3 #include <cstring> 4 #include <cstdio> 5 6 using namespace std; 7 8 const int maxn = 1e5 + 10; 9 const int maxq = 5010; 10 const

【cdq分治】【整体二分】bzoj 3110: [Zjoi2013] HYSBZ - 3110 K大数查询

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=3110 题意:有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c.如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少.注意是加入一个数,不是让这个数去求和. 题解:虽然是看cdq找到这题,但是感觉这个和平时做的三维偏序不大一样.这题其实是整体二分.就是首先,每次询问的答案应该是1,n之间的,然后

bzoj 1146 网络管理Network (CDQ 整体二分 + 树刨)

题目传送门 题意:求树上路径可修改的第k大值是多少. 题解:CDQ整体二分+树刨. 每一个位置上的数都会有一段持续区间 根据CDQ拆的思维,可以将这个数拆成出现的时间点和消失的时间点. 然后通过整体二分第k大思路 + 树炮询问路径上出现点的个数就好了. 说一下整体二分的思路. 先假设第k大的值是mid, 然后按照时间顺序,出现一个数<=mid标记这个数的位置为1, 消失一个数<=mid,标记这个数的位置为0. 然后对于询问来说,询问路径上的值, 与 k进行比较, 如果 值 >= k则说明