7.24 培训日记

分治。

T1 P3350

我们考虑分治,这是一张网格图,首先我们通过旋转使得较宽的那条边作为长,我们考虑取长的 (mid),然后在两边的点的最短路一定会通过中间的这一列点,所以我们考虑做中间这一列点的 spfa,然后回答一下询问,然后把两个点都在两边的递归来处理。递归信息写得详细没坏处,但还是注意要写正确。

同时我们跑最短路需要 spfa 而不是 dij,这是因为 spfa 在随机情况下是 (O(kn)) 的,这道题也没有卡 spfa,同时我们可以常数优化,我们跑完一遍最短路跑下一遍时,注意我们 (d) 数组的初始化没有必要是 (INF),我们可以是上一次跑 (d) 的再加上这次跑的点与上次跑的点之间的距离。

因为为了程序可读性,常数写大了,开 (O2) 才过。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 20010
#define M 100010
using namespace std;
// #define fre
// #define debug
// #define debug2

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Min(T a,T b){return a<b?a:b;}

const int fx[]={0,0,0,1,-1};
const int fy[]={0,1,-1,0,0};

struct Point{
    int x,y;
    inline Point(){}
    inline Point(int x,int y) : x(x),y(y) {}
    inline Point operator - (const Point &b)const{return Point(x-b.x,y-b.y);}
    inline void Init(){read(x);read(y);}
    inline bool In(int lx,int ly,int rx,int ry){return lx<=x&&x<=rx&&ly<=y&&y<=ry;}
};

int n,m,d[N],qt,ans[M];
vector<int> r[N],c[N];
queue<Point> q;
bool vis[N];

struct Ques{
    Point p1,p2;int id;
    inline Ques(){}
    inline Ques(Point p1,Point p2,int id) : p1(p1),p2(p2),id(id) {}
    inline bool In(int lx,int ly,int rx,int ry){return p1.In(lx,ly,rx,ry)&&p2.In(lx,ly,rx,ry);}
};
Ques ques[M],q1[M],q2[M];int t1,t2;

inline int GetI(Point a){return (a.x-1)*m+a.y;}

inline void Add(int lx,int ly,int rx,int ry,int x){
    for(int i=lx;i<=rx;i++){
        for(int j=ly;j<=ry;j++) d[GetI(Point(i,j))]+=x;
    }
}

inline int GetDis(Point a,Point b){
    if(a.x>b.x) swap(a,b);if(a.y>b.y) swap(a,b);
    Point now=b-a;if(now.y==1) return r[a.x][a.y];else return c[a.x][a.y];
}

inline void SPFA(Point k,int lx,int ly,int rx,int ry){
    d[GetI(k)]=0;q.push(k);vis[GetI(k)]=1;
    while(q.size()){
        Point top=q.front();q.pop();vis[GetI(top)]=0;
        for(int i=1,val;i<=4;i++){
            int dx=top.x+fx[i],dy=top.y+fy[i];
            if(dx>rx||dy>ry||dx<lx||dy<ly) continue;Point to=Point(dx,dy);
            if(d[GetI(to)]>(val=d[GetI(top)]+GetDis(top,to))){
                d[GetI(to)]=val;if(!vis[GetI(to)]){vis[GetI(to)]=1;q.push(to);}
            }
        }
    }

    #ifdef debug
        printf("now:(%d,%d) lx:%d ly:%d rx:%d ry:%d
",k.x,k.y,lx,ly,rx,ry);
        for(int i=1;i<=n;i++){for(int j=1;j<=m;j++) printf("%d ",d[GetI(Point(i,j))]);puts("");}
    #endif
}

inline void DivideQuestions(int l,int r,int lx,int ly,int rx,int ry,int op){
    if(op==1){t1=0;for(int i=l;i<=r;i++) if(ques[i].In(lx,ly,rx,ry)) q1[++t1]=ques[i];}
    else if(op==2){t2=0;for(int i=l;i<=r;i++) if(ques[i].In(lx,ly,rx,ry)) q2[++t2]=ques[i];}
}

inline void Merge(Ques *q1,Ques *q2,int t1,int t2,int l,int &mid,int &End){
    for(int i=l;i<=l+t1-1;i++) ques[i]=q1[i-l+1];for(int i=l+t1;i<=l+t1+t2-1;i++) ques[i]=q2[i-t1-l+1];mid=l+t1-1;End=l+t1+t2-1;
}

inline void UpdateAns(int l,int r){
    for(int j=l;j<=r;j++){ans[ques[j].id]=Min(ans[ques[j].id],d[GetI(ques[j].p1)]+d[GetI(ques[j].p2)]);}
}

inline void Solve(int lx,int ly,int rx,int ry,int l,int r){
    #ifdef debug
        printf("here:%d %d %d %d %d %d
",lx,ly,rx,ry,l,r);
    #endif
    if(l>r||lx<=0||ly<=0||lx>n||ly>m||rx<=0||rx>n||ry<=0||ry>m||rx<lx||ry<ly) return;
    #ifdef debug
        for(int i=l;i<=r;i++) printf("%d ",ques[i].id);puts("");
    #endif
    t1=t2=0;
    int lenn=rx-lx+1,lenm=ry-ly+1;
    if(lenn<=lenm){
        int mid=(ly+ry)>>1;
        for(int i=lx;i<=rx;i++){
            if(i==lx) Add(lx,ly,rx,ry,INF);
            Point now(i,mid);SPFA(now,lx,ly,rx,ry);UpdateAns(l,r);
            #ifdef debug
                printf("ans:");for(int i=1;i<=qt;i++) printf("%d ",ans[i]);puts("");
            #endif
            if(i!=rx) Add(lx,ly,rx,ry,d[GetI(Point(i+1,mid))]);
        }
        #ifdef debug2
            printf("here
");
        #endif
        int posi,End;DivideQuestions(l,r,lx,ly,rx,mid-1,1);DivideQuestions(l,r,lx,mid+1,rx,ry,2);
        #ifdef debug2
            printf("here
");
        #endif
        Merge(q1,q2,t1,t2,l,posi,End);
        #ifdef debug2
            printf("here
");
        #endif


        Solve(lx,ly,rx,mid-1,l,posi);Solve(lx,mid+1,rx,ry,posi+1,End);
    }
    else{
        int mid=(lx+rx)>>1;
        for(int i=ly;i<=ry;i++){
            if(i==ly) Add(lx,ly,rx,ry,INF);
            Point now(mid,i);SPFA(now,lx,ly,rx,ry);UpdateAns(l,r);
            if(i!=ry) Add(lx,ly,rx,ry,d[GetI(Point(mid,i+1))]);
        }
        int posi,End;DivideQuestions(l,r,lx,ly,mid-1,ry,1);DivideQuestions(l,r,mid+1,ly,rx,ry,2);Merge(q1,q2,t1,t2,l,posi,End);
        Solve(lx,ly,mid-1,ry,l,posi);Solve(mid+1,ly,rx,ry,posi+1,End);
    }
}


int main(){
    #ifdef fre
        freopen("my.in","r",stdin);
        freopen("my.out","w",stdout);
    #endif

    read(n);read(m);
    for(int i=1;i<=n;i++){
        r[i].push_back(INF);for(int j=1;j<=m-1;j++){int x;read(x);r[i].push_back(x);}
    }
    for(int i=1;i<=n-1;i++){
        c[i].push_back(INF);for(int j=1;j<=m;j++){int x;read(x);c[i].push_back(x);}
    }read(qt);
    for(int i=1;i<=qt;i++){ques[i].p1.Init();ques[i].p2.Init();ques[i].id=i;}

    memset(ans,INF,sizeof(ans));Solve(1,1,n,m,1,qt);
    for(int i=1;i<=qt;i++){printf("%d
",ans[i]);}
    return 0;
}

T2 P3810

三维偏序,cdq 分治的板子题,我们首先保证第一维有序,然后剩下的我们先按照第二维排序,然后用树状数组维护第一维。复杂度 (O(nlog^3 n)),其中按照第二维排序可以用归并排序,复杂度可以少一个 (log)

这里注意,每次 cdq 我们考虑的是左边对右边的贡献,而左右两边对自己的贡献我们递归处理。因为一开始按照第一维排序,所以左边的第一维永远要比右边的第一维小,所以我们在 cdq 分治过程中不用考虑第一维。

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int n,k,f[N],tong[N];

struct BIT{
    int p[N];
    inline int lowbit(int x){return x&(-x);}
    inline void add(int x,int val){for(int i=x;i<=k;i+=lowbit(i))p[i]+=val;}
    inline int ask_sum(int x){int ans=0;for(int i=x;i>=1;i-=lowbit(i)) ans+=p[i];return ans;}
};
BIT bit;

struct flower{
    int a,b,c,cnt,ans;
    inline bool operator == (const flower &b) const{
        return (a==b.a)&&(this->b==b.b)&&(c==b.c);
    }
};
flower fl[N];

inline bool cmp1(flower a,flower b){
    if(a.a!=b.a) return a.a<b.a;
    else if(a.b!=b.b) return a.b<b.b;
    else return a.c<b.c;
}

inline bool cmp2(flower a,flower b){
    if(a.b!=b.b) return a.b<b.b;
    else return a.c<b.c;
}

inline void cdq(int l,int r){
    if(l==r) return;
    int mid=(l+r)>>1;
    cdq(l,mid);cdq(mid+1,r);
    sort(fl+l,fl+mid+1,cmp2);
    sort(fl+mid+1,fl+r+1,cmp2);
    int i=l,j=mid+1;
    for(;j<=r;j++){
        while(fl[i].b<=fl[j].b&&i<=mid){
            bit.add(fl[i].c,fl[i].cnt);i++;
        }
        fl[j].ans+=bit.ask_sum(fl[j].c);
    }
    for(int w=l;w<i;w++) bit.add(fl[w].c,-fl[w].cnt);
}

int main(){
    // freopen("my.in","r",stdin);
    // freopen("my.out","w",stdout);
    read(n);read(k);
    for(int i=1;i<=n;i++){
        read(fl[i].a);read(fl[i].b);read(fl[i].c);
    }
    sort(fl+1,fl+n+1,cmp1);
    int tail=0;fl[++tail]=fl[1];fl[tail].cnt=1;
    for(int i=2;i<=n;i++){
        if(fl[i]==fl[i-1]){
            fl[tail].cnt++;
        }
        else fl[++tail]=fl[i],fl[tail].cnt=1;
    }
    cdq(1,tail);
    for(int i=1;i<=tail;i++) tong[fl[i].ans+fl[i].cnt-1]+=fl[i].cnt;
    for(int i=0;i<=n-1;i++) printf("%d
",tong[i]);
    return 0;
}

T3 P5163 WD与地图

王泽远的毒瘤题目,据说还是他高一的时候出的。

这道题目是一道整体二分加权值线段树二分。

我们首先考虑如果所有的有向边改成无向边怎么做,首先一定是时光倒流,我们把删边改成加边来处理,其次,因为一条无向边可以合并两个连通块,所以我们用并查集来维护连通块的黑帮,对于每个点我们建一个权值线段树,然后再合并连通块的时候我们同样合并两个连通块所代表的权值线段树。每次修改相当于两次单点修改,每次询问相当于线段树上二分。

而对于有向边,我们就不能够这样做了,因为一条边可能不会合并两个集合,但是如果我们优先处理处每条边什么时候会被缩起来,那么这个题就可以做了。

显然答案具有二分性,我们考虑整体二分,因为要保证时间复杂度,我们首先让删除时间大于 (mid) 的放到图中,然后跑一遍 tarjan,如果这条边被缩起来了,我们看看时间会不会更大,否则时间只会更小。注意我们已经时光倒流,删除时间越小就是让加入时间越大。而我们由于方便需要让时间统一,所以我们统一用删除时间来度量。我第一遍写的时候由于让时间不统一于是非常头疼,最后回答询问难以处理,最后只能选择重构代码。这个题的代码实现还是非常有技巧的。

需要注意的是我们要保证复杂度,我们对删除时间大于 (mid) 的边跑 tarjan 后首先先向左递归,注意跑 tarjan 的时候我们用可撤销并查集来维护连通块,这样向左递归的时候我们不需要重新 tarjan,这样每一层我们的 tarjan 规模最多为这一层所有的边,复杂度是 (nlog n) 的。跑完左边之后我们用可撤销并查集撤销影响,然后去跑右边。注意一条边链接两个点在这道题中我们要把其所在连通块连一起,也就是说我们需要用可撤销并查集来维护缩点信息。

这个复杂度是 (O(nlog^2 n))

接下来我们考虑回答询问,我们首先解释为什么时间要统一,这样我们可以让每条边被缩起来的时间作为其删除时间,因为每条边只当其被缩起来的时候才能对答案产生贡献。我们接着对询问序列排序,首先按照时间排序,然后我们优先处理加边,再处理询问。这样就可以只扫一遍询问序列。这是一个处理的技巧。

回答询问时我们用权值线段树上二分来维护。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 100010
#define M 200010
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Max(T a,T b){return a<b?b:a;}
template<typename T> inline T Min(T a,T b){return a<b?a:b;}

struct Ques{
    int op,x,y,time;
    inline Ques(){}
    inline Ques(int op,int x,int y,int time) : op(op),x(x),y(y),time(time) {}
    inline bool operator < (const Ques &b)const{
        if(time!=b.time) return time>b.time;
        else return op<b.op;
    }
}ques[M<<1];

struct edge{
    int from,to,time,ShrikingTime;
    inline edge(){}
    inline edge(int from,int to,int time) : from(from),to(to),time(time) {}
}e[M];
int n,m,q;

typedef pair<int,int> P;

namespace Binary{
    struct VoidableDSU{
        int fa[N],size[N],top;P sta[N];
        inline VoidableDSU(){top=0;}
        inline void Init(int n){for(int i=1;i<=n;i++) fa[i]=i,size[i]=1;}
        inline int Find(int x){return fa[x]==x?x:Find(fa[x]);}
        inline bool Same(int a,int b){
            int faa=Find(a),fab=Find(b);
            return faa==fab;
        }
        inline void Merge(int a,int b){
            int faa=Find(a),fab=Find(b);
            if(faa==fab) return;
            if(size[faa]<size[fab]) swap(faa,fab);
            size[faa]+=size[fab];fa[fab]=faa;sta[++top]=make_pair(faa,fab);
        }
        inline void Cancel(int sum){
            for(int i=1;i<=sum&&top;i++){
                int faa=sta[top].first,fab=sta[top].second;
                size[faa]-=size[fab];fa[fab]=fab;top--;
            }
        }
    }vdsu;
    edge t[M<<1];
    vector<int> Edge[N];
    int dfn[N],low[N],sta[N],top,tot;
    bool InStack[N];
    inline void tarjan(int k){
        dfn[k]=low[k]=++tot;sta[++top]=k;InStack[k]=1;
        for(auto to:Edge[k]) if(!dfn[to]) tarjan(to),low[k]=Min(low[k],low[to]);
        else if(InStack[to]) low[k]=Min(low[k],dfn[to]);
        if(dfn[k]==low[k]){
            int now;
            do{
                now=sta[top--];InStack[now]=0;vdsu.Merge(now,k);
            }while(now!=k);
        }
    }
    inline void Solve(int l,int r,int z,int y){
        int lasttop=vdsu.top;
        if(l==r){
            for(int i=z;i<=y;i++) e[i].ShrikingTime=l;
            return;
        }
        int mid=(l+r)>>1;
        vector<int> now;
        for(int i=z;i<=y;i++){
            if(e[i].time<=mid) continue;
            int fa1=vdsu.Find(e[i].from),fa2=vdsu.Find(e[i].to);
            Edge[fa1].push_back(fa2);
            now.push_back(fa1);now.push_back(fa2);
        }
        for(auto k:now) if(!dfn[k]) tarjan(k);
        tot=0;
        for(auto k:now) Edge[k].clear(),dfn[k]=0;
        int p1=z,p2=y;
        for(int i=z;i<=y;i++){
            if(e[i].time>mid&&vdsu.Same(e[i].from,e[i].to)) t[p2--]=e[i];
            else t[p1++]=e[i];
        }
        for(int i=z;i<=y;i++) e[i]=t[i];
        Solve(l,mid,z,p1-1);
        vdsu.Cancel(vdsu.top-lasttop);
        Solve(mid+1,r,p2+1,y);
    }
}

namespace DS{
    const int D=1e9;
    struct DSU{
        int fa[N],siz[N];
        inline void Init(int n){for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;}
        inline int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
        inline bool Same(int a,int b){
            int faa=Find(a),fab=Find(b);
            return faa==fab;
        }
        inline void ClearNode(int k){fa[k]=k;siz[k]=1;}
    }dsu;
    int root[N],tot,a[N];
    struct node{
        int ls,rs,size;
        ll sum;
    }p[N*400];
    inline int NewNode(){return ++tot;}
    inline void PushUp(int k){
        p[k].size=p[p[k].ls].size+p[p[k].rs].size;
        p[k].sum=p[p[k].ls].sum+p[p[k].rs].sum;
    }
    inline void Insert(int &k,int l,int r,int w,int x){
        if(!k) k=NewNode();
        if(l==r){p[k].size+=x;p[k].sum=l*p[k].size;return;}
        int mid=(l+r)>>1;
        if(w<=mid) Insert(p[k].ls,l,mid,w,x);
        else Insert(p[k].rs,mid+1,r,w,x);PushUp(k);
    }
    inline int Merge(int a,int b){
        if(!a||!b) return a+b;
        p[a].size+=p[b].size;p[a].sum+=p[b].sum;
        p[a].ls=Merge(p[a].ls,p[b].ls);
        p[a].rs=Merge(p[a].rs,p[b].rs);
        return a;
    }
    inline ll Binary(int k,int l,int r,int val){
        if(!k) return 0;
        if(l==r) return 1ll*val*l;
        int mid=(l+r)>>1;
        if(p[p[k].rs].size>=val) return Binary(p[k].rs,mid+1,r,val);
        else return Binary(p[k].ls,l,mid,val-p[p[k].rs].size)+p[p[k].rs].sum;
    }
    inline void Solve(int tot){
        sort(ques+1,ques+tot+1);
        dsu.Init(n);
        for(int i=1;i<=n;i++){Insert(root[i],0,D,a[i],1);}
        stack<ll> ans;
        for(int i=1;i<=tot;i++){
            int &op=ques[i].op,&x=ques[i].x,&y=ques[i].y;
            int fax=dsu.Find(x);
            if(op==1){
                int fay=dsu.Find(y);
                if(fax==fay) continue;
                if(dsu.siz[fax]<dsu.siz[fay]) swap(fax,fay),swap(x,y);
                dsu.siz[fax]+=dsu.siz[fay];dsu.fa[fay]=fax;root[fax]=Merge(root[fax],root[fay]);
            }
            else if(op==2) Insert(root[fax],0,D,a[x],-1),Insert(root[fax],0,D,a[x]-=y,1);
            else ans.push(Binary(root[fax],0,D,y));
        }
        while(ans.size()) printf("%lld
",ans.top()),ans.pop();
    }
}

// #define fre
// #define debug

signed main(){
    #ifdef fre
        freopen("my.in","r",stdin);
        freopen("my.out","w",stdout);
    #endif
    read(n);read(m);read(q);
    for(int i=1;i<=n;i++) read(DS::a[i]);
    map<P,int> Map;

    for(int i=1,x,y;i<=m;i++){
        int from,to;read(from);read(to);
        e[Map[make_pair(from,to)]=i]=edge(from,to,q+1);
    }
    int tot=0;
    for(int i=1,op,a,b;i<=q;i++){
        read(op);read(a);read(b);
        if(op==1) e[Map[make_pair(a,b)]].time=i;
        else if(op==2) DS::a[a]+=b,ques[++tot]=Ques(op,a,b,i);
        else ques[++tot]=Ques(op,a,b,i);
    }
    Binary::vdsu.Init(n);
    Binary::Solve(0,q+1,1,m);
    #ifdef debug
        for(int i=1;i<=m;i++) printf("from:%d to:%d time:%d nt:%d
",e[i].from,e[i].to,e[i].time,e[i].ShrikingTime);
    #endif


    for(int i=1;i<=m;i++) ques[++tot]=Ques(1,e[i].from,e[i].to,e[i].ShrikingTime);
    DS::Solve(tot);
    return 0;
}

T4 SP2371 LIS2

最长偏序链,cdq 分治优化 dp 裸题。

其实 cdq 分治优化 dp 的方程非常具有特色,首先,他必须满足只有一维,其次,他必须满足限制条件有三个。这样的我们才可以用 cdq 分治来进行优化。但其实大多数题目不会像这个题目一样裸,我们通常需要把一个多维的 dp 改成一维并适当增加或减少限制条件。

当然,你首先得保证你 cdq 分治优化 dp 得打熟了。cdq 分治优化 dp 的优化方式和大多数优化思想一样,就是维护决策集合,减少枚举量,减少不必要的枚举。

这个题我们首先写出 dp 方程:

[f_{i}=max_{j<i,x_j<x_i,y_j<y_i}{ f_j+1 } ]

我们考虑用 cdq 分治优化 dp ,对于一层来说,我们首先先递归左边,然后我们考虑左边对右边的影响,然后我们递归右边。在考虑影响的时候,我们需要对整个序列按照第二维排序,然后用树状数组更新答案,注意因为要求 (x_j<x_i),所以如果 (mid) 左边和右边的某个 (x) 相等,优先把右边的排到前面。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 100010
#define M number
using namespace std;
// #define fre
// #define debug

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Max(T a,T b){return a<b?b:a;}

struct Point{
    int x,y,id;
    inline Point(){}
    inline Point(int x,int y) : x(x),y(y) {}
    inline void Init(){read(x);read(y);}
    inline bool operator < (const Point &b)const{
        if(x!=b.x) return x<b.x;if(id!=b.id) return id>b.id;return y<b.y;
    }
};Point a[N],b[N];

int n,f[N],c[N<<1],ans=-INF,maxx=-INF;

struct BIT{
    int p[N<<1];
    inline int lowbit(int x){return x&(-x);}
    inline void Add(int w,int x){for(int i=w;i<=maxx;i+=lowbit(i)) p[i]=Max(p[i],x);}
    inline int GetMax(int w){
        int res=0;for(int i=w;i>=1;i-=lowbit(i)) res=Max(res,p[i]);return res;
    }
    inline void Clear(int w){for(int i=w;i<=maxx;i+=lowbit(i)) p[i]=0;}
}bit;

inline void Solve(int l,int r){
    if(l==r){f[a[l].id]=Max(f[a[l].id],1);return;}
    int mid=(l+r)>>1;Solve(l,mid);for(int i=l;i<=r;i++) b[i-l+1]=a[i];int len=r-l+1;
    sort(b+1,b+len+1);
    for(int i=1;i<=len;i++){
        if(b[i].id<=mid) bit.Add(b[i].y,f[b[i].id]);
        else f[b[i].id]=Max(f[b[i].id],bit.GetMax(b[i].y-1)+1);
    }
    for(int i=1;i<=len;i++) if(b[i].id<=mid) bit.Clear(b[i].y);Solve(mid+1,r);
}

int main(){
    #ifdef fre
        freopen("my.in","r",stdin);
        freopen("my.out","w",stdout);
    #endif

    read(n);for(int i=1;i<=n;i++){a[i].Init();a[i].id=i;c[(i<<1)-1]=a[i].x;c[i<<1]=a[i].y;}
    sort(c+1,c+(n<<1)+1);int len=unique(c+1,c+(n<<1)+1)-c-1;
    for(int i=1;i<=n;i++){
        a[i].x=lower_bound(c+1,c+len+1,a[i].x)-c;
        a[i].y=lower_bound(c+1,c+len+1,a[i].y)-c;
        maxx=Max(maxx,a[i].y);
    }Solve(1,n);
    #ifdef debug
        printf("f:");for(int i=1;i<=n;i++) printf("%d ",f[i]);puts("");
    #endif
    for(int i=1;i<=n;i++) ans=Max(ans,f[i]);printf("%d
",ans);return 0;
}

T5 P5787

线段树分治已经讲过,这里不再赘述。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 700010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int n,m,k;

int fa[N],deep[N],top;
pair<int,int> sta[N];
struct bingchaji{
    inline int find(int a){
        return a==fa[a]?a:find(fa[a]);
    }
    inline bool belong_to_the_same(int a,int b){
        int faa=find(a),fab=find(b);
        return faa==fab;
    }
    inline bool merge(int a,int b){
        int faa=find(a),fab=find(b);
        if(faa==fab) return 0;
        if(deep[faa]>deep[fab]) swap(faa,fab);
        fa[faa]=fab;sta[++top]=make_pair(faa,deep[fab]);
        if(deep[faa]==deep[fab]) deep[fab]++;
        return 1;
    }
    inline void chushihua(int n){
        for(int i=1;i<=n;i++) fa[i]=i,deep[i]=0;top=0;
    }
    inline void chexiao(int k){
        for(int i=1;i<=k;i++){
            int lastdian=sta[top].first,lastdeep=sta[top].second;
            deep[fa[lastdian]]=lastdeep;fa[lastdian]=lastdian;top--;
        }
    }
};
bingchaji bcj;

struct edge{
    int from,to;
    inline edge(){}
    inline edge(int from,int to) : from(from),to(to) {}
};

struct node{
    int l,r;vector<edge> bian;
};
node p[N<<2];

int tot,root;
bool ans[N];

inline void change(int &k,int l,int r,int z,int y,int u,int v){
    if(!k) k=++tot;
    if(l==z&&r==y){
        p[k].bian.push_back(edge(u,v));return;
    }
    int mid=(l+r)>>1;
    if(y<=mid) change(p[k].l,l,mid,z,y,u,v);
    else if(z>mid) change(p[k].r,mid+1,r,z,y,u,v);
    else change(p[k].l,l,mid,z,mid,u,v),change(p[k].r,mid+1,r,mid+1,y,u,v);
}

inline void solve(int &k,int l,int r,bool op){
    if(!k) k=++tot;
    int mid=(l+r)>>1;
    int lasttop=top;
    for(int j=0;j<p[k].bian.size()&&op;j++){
        edge nowbian=p[k].bian[j];
        if(bcj.belong_to_the_same(nowbian.from,nowbian.to)){
            op=0;break;
        }
        bcj.merge(nowbian.from,nowbian.to+n);
        bcj.merge(nowbian.to,nowbian.from+n);
    }
    if(l==r){ans[l]=op;}
    else if(op){solve(p[k].l,l,mid,op);solve(p[k].r,mid+1,r,op);}
    bcj.chexiao(top-lasttop);
}

int main(){
    read(n);read(m);read(k);
    for(int i=1;i<=m;i++){
        int x,y,l,r;read(x);read(y);read(l);read(r);
        change(root,0,k,l,r,x,y);
    }
    bcj.chushihua(n<<1);
    solve(root,0,k,1);
    for(int i=0;i<=k-1;i++) if(ans[i]||ans[i+1]) printf("Yes
");else printf("No
");
    return 0;
}

T6 CF1365G

二进制分组的好题,王泽远说每场 cf 都有二进制分组的题目,下次留意一下。

我们首先考虑如果你的询问次数不超过 (20),你怎么做,我们可以选择询问所有下标二进制第 (j) 位为 (1) 和为 (0) 的,分别或起来的答案,因为位数不会超过 (10),所以总次数不会超过 (20)。询问完后对于一个下标,如果第 (i) 位为 (1),那么就把第 (i) 位为 (0) 的全部或起来的答案或到当前答案上,如果为 (0) 同理。因为两个下标一定会有一位不一样,所以我们除了这一位以外的每一个数都一定被或上了。

我们考虑询问次数不超过 (13) 怎么做。我们仍然用上面的思想,我们注意到上面的这种方法实际上是给每个位置一个标记,两个位置的标记一定会有所不同。这引导我们这样来做这个题。

我们考虑 (13) 位二进制数中有 (6) 个一个二进制个数,不难发现这个个数超过了 (1000),所以我们用这个作为标记,我们给每个位置一个标记,然后我们每次询问把标记第 (i) 位为 (1) 的或起来的答案,对于一个位置,如果该位置第 (i) 位为 (0),那么我么就取第 (i)(1) 的或起来的答案,否则就不管,因为两个标记一定有一位不相同,所以这种方法显然正确。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 1010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

int ID[N<<1],tail,n,a[N],b[20],p[N];

inline void dfs(int posi,int k,int now){
    if(k==6){ID[++tail]=now;return;}
    if(k<6) dfs(posi+1,k+1,now+(1<<posi));
    if(12-posi+1>(6-k)) dfs(posi+1,k,now);
}

signed main(){
    read(n);dfs(0,0,0);
    for(int i=0;i<=12;i++){
        int now=0;
        for(int j=1;j<=n;j++) if((ID[j]>>i)&1) a[++now]=j;
        if(now>=1){
            printf("? %lld ",now);
            for(int j=1;j<=now;j++){
                printf("%lld",a[j]);if(j!=now) printf(" ");
            }puts("");fflush(stdout);
            read(b[i]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=0;j<=12;j++) if(((ID[i]>>j)&1)==0) p[i]|=b[j];
    }
    printf("! ");
    for(int i=1;i<=n;i++){
        printf("%lld",p[i]);if(i!=n) printf(" ");
    }
    fflush(stdout);
    return 0;
}//

T7 HDU6800

cdq 分治优化毒瘤题目。

我们首先考虑一个 dp,显然这个 dp 的状态需要包含这个序列两端的位置,所以令 (f_{i,j}) 表示考虑完前 (i) 个点,另一个序列的末尾为 (j) 时的最优解。

由于需要优化,所以我们需要知道那些状态能够更新 (f_{i,j})

首先如果 (j<i-1),那么状态 (f_{i,j}) 显然只能从 (f_{i-1,j}) 转移过来。如果 (j=i-1),那么这个状态可以从 (f_{i-1,k}) 转移过来,其中 (k) 是需要我们去枚举的。

二维 dp 的枚举要么依靠数据结构,要么四边形不等式,考虑过后都不行,我们考虑压维。

不难发现特殊的只有 (f_{i,i-1}),不难发现,如果我们得到了所有的 (f_{i,i-1}),我们其实可以枚举靠前端点全选完之前的状态是什么,然后从 (f_{i,i-1}) 计算答案,这其实就是一个前缀和的事情,不懂的可以待会看看代码。

我们令 (g_i=f_{i,i-1}),我们考虑 (g) 如何转移。不难发现,转移式子为:

[g_{i}=minlimits_{j}{ g_j+d(j-1,i)+d(j,j+1)+d(j+1,j+2)+...d(i-2,i-1) } ]

其中 (d(i,j)) 表示点 (i) 到点 (j) 的马哈顿距离。

不难发现后面可以使用前缀和优化,我们有:

[g_i=minlimits_{j}{ g_j+d(j-1,i)+S_{i-1}-S_{j-1} } ]

至此,这个 dp 还是一个 (n^2) 的。我们考虑把 (d(j-1,i)) 的贡献拆开得到四个转移方程,由于篇幅关系,这里只写一个作为例子:

[g_i=minlimits_{jle i-2,x_{j-1}<x_i,y_{j-1}<y_i}{ g_j+x_i-x_{j-1}+y_i-y_{j-1}+S_{i-1}-S_{j-1} } ]

我们可以把与 (j) 无关的项全部提到外面:

[g_i=minlimits_{jle i-2,x_{j-1}<x_i,y_{j-1}<y_i}{ g_j-x_{j-1}-y_{j-1}-S_{j-1} }+x_i+S_{i-1}+y_i ]

至此,这个 dp 就可用 cdq 分治来优化了。

不过到这里在实现上还有点困难,这是因为 (jle i-2) 难已处理,我们 cdq 分治一定是枚举 (j-1) 这个点,我曾经尝试过再记录一个 (id),然后把右边的 (id) 集体减一把条件变成 (i-1),但是这样难以排序,不难发现其实除了 (mid+1) 这个位置,其余的位置都可以正常 cdq 分治优化那样转移,所以我们考虑单独处理 (mid+1) 这个位置。其余位置我们照常转移。

由递推式容易知道,就算 (j) 枚举到 (i-1) 也没有关系。

如果不单独处理 (mid+1) 这个位置,会出现一个结果,当我们枚举 (j-1) 的时候我们枚举到 (mid),这个时候我们想更新 (mid+2),但我们要用到 (mid+1) 的 dp 值。这个时候就会出现问题。这也是我们优先处理 (mid+1) 的理由。

代码:

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 200010
#define M number
using namespace std;
// #define fre 
// #define debug

const int INF=1e16;

template<typename T> inline void read(T &x) {
    x=0; int f=1;
    char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    x*=f;
}

template<typename T> inline T Min(T a,T b){return a<b?a:b;}
template<typename T> inline T Max(T a,T b){return a<b?b:a;}

struct Point{
    int x,y,id,x0,y0;
    inline Point(){}
    inline Point(int x,int y,int id) : x(x),y(y),id(id) {}
    inline void Init(){read(x);read(y);}
}a[N],b[N];

int t,n,s[N],g[N],maxy,c[N<<1];

struct BIT{
    int p[N<<1];
    inline int lowbit(int x){return x&(-x);}
    inline void Add1(int w,int x){for(int i=w;i<=maxy;i+=lowbit(i)) p[i]=Min(p[i],x);}
    inline int GetMin1(int w){int res=INF;for(int i=w;i>=1;i-=lowbit(i)) res=Min(res,p[i]);return res;}
    inline void Add2(int w,int x){for(int i=w;i>=1;i-=lowbit(i)) p[i]=Min(p[i],x);}
    inline int GetMin2(int w){int res=INF;for(int i=w;i<=maxy;i+=lowbit(i)) res=Min(res,p[i]);return res;}
    inline void Clear1(int w){for(int i=w;i<=maxy;i+=lowbit(i)) p[i]=INF;}
    inline void Clear2(int w){for(int i=w;i>=1;i-=lowbit(i)) p[i]=INF;}
    inline void Clear(int l,int r){for(int i=l;i<=r;i++) p[i]=INF;}
}bit;

inline int GetDis(Point a,Point b){return abs(a.x-b.x)+abs(a.y-b.y);}

inline void Init(){
    read(n);for(int i=1;i<=n;i++){a[i].Init();a[i].id=i;a[i].id=i;c[(i<<1)-1]=a[i].x;c[(i<<1)]=a[i].y;}
    for(int i=2;i<=n;i++) s[i]=s[i-1]+GetDis(a[i-1],a[i]);
    for(int i=1;i<=n;i++) g[i]=s[i-1];maxy=-INF;
    sort(c+1,c+(n<<1)+1);int len=unique(c+1,c+(n<<1)+1)-c-1;
    for(int i=1;i<=n;i++){a[i].x0=lower_bound(c+1,c+len+1,a[i].x)-c;a[i].y0=lower_bound(c+1,c+len+1,a[i].y)-c;}
    for(int i=1;i<=n;i++) maxy=Max(maxy,a[i].y0);bit.Clear(1,maxy);
}

inline bool Cmp1(Point a,Point b){if(a.x!=b.x) return a.x<b.x;if(a.y!=b.y) return a.y<b.y;return a.id<b.id;}
inline bool Cmp2(Point a,Point b){if(a.x!=b.x) return a.x<b.x;if(a.y!=b.y) return a.y>b.y;return a.id<b.id;}
inline bool Cmp3(Point a,Point b){if(a.x!=b.x) return a.x>b.x;if(a.y!=b.y) return a.y<b.y;return a.id<b.id;}
inline bool Cmp4(Point a,Point b){if(a.x!=b.x) return a.x>b.x;if(a.y!=b.y) return a.y>b.y;return a.id<b.id;}

inline void Solve(int l,int r){
    #ifdef debug
        printf("%d %d
",l,r);
    #endif
    if(l==r){return;}
    int mid=(l+r)>>1;Solve(l,mid);
    for(int i=l;i<=r;i++) b[i-l+1]=a[i];int len=r-l+1;
    for(int i=l;i<=mid-1;i++)
        g[mid+1]=Min(g[mid+1],g[i+1]+GetDis(a[i],a[mid+1])+s[mid]-s[i+1]);
    sort(b+1,b+len+1,Cmp1);
    for(int i=1;i<=len;i++){
        if(b[i].id<=mid) bit.Add1(b[i].y0,g[b[i].id+1]-s[b[i].id+1]-a[b[i].id].x-a[b[i].id].y);
        else g[b[i].id]=Min(g[b[i].id],bit.GetMin1(b[i].y0)+b[i].x+b[i].y+s[b[i].id-1]);
    }for(int i=1;i<=len;i++) if(b[i].id<=mid) bit.Clear1(b[i].y0);
    sort(b+1,b+len+1,Cmp2);
    for(int i=1;i<=len;i++){
        if(b[i].id<=mid) bit.Add2(b[i].y0,g[b[i].id+1]-s[b[i].id+1]-a[b[i].id].x+a[b[i].id].y);
        else g[b[i].id]=Min(g[b[i].id],bit.GetMin2(b[i].y0)+b[i].x-b[i].y+s[b[i].id-1]);
    }for(int i=1;i<=len;i++) if(b[i].id<=mid) bit.Clear2(b[i].y0);
    sort(b+1,b+len+1,Cmp3);
    for(int i=1;i<=len;i++){
        if(b[i].id<=mid) bit.Add1(b[i].y0,g[b[i].id+1]-s[b[i].id+1]+a[b[i].id].x-a[b[i].id].y);
        else g[b[i].id]=Min(g[b[i].id],bit.GetMin1(b[i].y0)-b[i].x+b[i].y+s[b[i].id-1]);
    }for(int i=1;i<=len;i++) if(b[i].id<=mid) bit.Clear1(b[i].y0);
    sort(b+1,b+len+1,Cmp4);
    for(int i=1;i<=len;i++){
        if(b[i].id<=mid) bit.Add2(b[i].y0,g[b[i].id+1]-s[b[i].id+1]+a[b[i].id].x+a[b[i].id].y);
        else g[b[i].id]=Min(g[b[i].id],bit.GetMin2(b[i].y0)-b[i].x-b[i].y+s[b[i].id-1]);
    }for(int i=1;i<=len;i++) if(b[i].id<=mid) bit.Clear2(b[i].y0);
    Solve(mid+1,r);
}

signed main(){
    #ifdef fre
        freopen("my.in","r",stdin);
        freopen("my.out","w",stdout);
    #endif
    read(t);
    while(t--){
        Init();Solve(1,n);
        #ifdef debug
            printf("g: ");for(int i=1;i<=n;i++) printf("%d ",g[i]);puts("");
        #endif
        int ans=INF;for(int i=1;i<=n;i++) ans=Min(ans,g[i]+s[n]-s[i]);
        printf("%lld
",ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/TianMeng-hyl/p/15240370.html