数据结构杂题选做

楼房重建

题面

先搞清楚题目要求的是什么。令 \(k_0=0,k_i=\frac{h_i}{i}\),则题目求的一个从 \(0\) 开始的单调上升序列的长度减一。

最暴力的做法就是直接维护上升的序列,修改后从修改处开始重新维护,但时间复杂度不对,考虑优化。

先忽略从 \(0\) 开始的限制条件。

\(mx_{\left[l,r\right]}\) 表示区间 \(\left[l,r\right]\) 内的最大值,对于区间 \(\left[l,k\right]\)\(\left[k+1,r\right]\) 可以发现当 \(mx_{\left[l,k\right]}<mx_{\left[k+1,r\right]}\) 时,区间 \(\left[l,r\right]\) 的答案是区间 \(\left[l,k\right]\) 的答案合并而来的。对于不满足上述条件的区间,可以在 \(\left[k+1,r\right]\) 上找到第一个大于 \(mx_{\left[l,k\right]}\) 的数的位置 \(x\),得到 \(\left[x,r\right]\) 的答案,区间 \(\left[l,r\right]\) 的答案也可以转移得到。既然可以区间合并,考虑线段树。

对于区间 \(\left[l,r\right]\),若 \(l=r\),则有值答案赋为 \(1\),没值答案赋为 \(0\)。否则考虑两个区间最大值之间的关系,按照上述方法合并,只需要找到 \(x\) 就可以了。对于 \(\left[x,y\right]\),因为所有子区间的答案都知道了,若左区间最大值大于要求的值,则右区间对 \(\left[x,y\right]\) 的答案的贡献可以直接计入,再往左区间递归寻找,否则往右区间递归寻找。每次修改之后重新维护答案即可,答案为区间 \(\left[1,n\right]\) 的答案。

update on 2023.4.9

有人告诉我原来这个就是兔队线段树,get新知识了

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define LD long double
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
    }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
const int N=2e5+10;
int n,q;
namespace Seg_Tree{
    struct node{
        int tot;
        LD mx;
    }tr[N<<2];
    int ls(int p){
        return p<<1;
    }
    int rs(int p){
        return p<<1|1;
    }
    int query(int p,int l,int r,LD mx){
        if(l==r){
            return tr[p].mx>mx;
        }
        int mid=(l+r)>>1;
        if(tr[ls(p)].mx<=mx){
            return query(rs(p),mid+1,r,mx);
        }
        else{
            return query(ls(p),l,mid,mx)+tr[p].tot-tr[ls(p)].tot;
        }
    }
    void update(int p,int l,int r,int pos,LD val){
        if(l==r){
            tr[p].tot=1;
            tr[p].mx=val;
            return;
        }
        int mid=(l+r)>>1;
        if(pos<=mid){
            update(ls(p),l,mid,pos,val);
        }
        else{
            update(rs(p),mid+1,r,pos,val);
        }
        tr[p].mx=max(tr[ls(p)].mx,tr[rs(p)].mx);
        tr[p].tot=tr[ls(p)].tot+query(rs(p),mid+1,r,tr[ls(p)].mx);
    }
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    read(n),read(q);
    while(q--){
        int pos,h;
        read(pos),read(h);
        Seg_Tree::update(1,1,n,pos,1.0L*h/pos);
        write_endl(Seg_Tree::tr[1].tot);
    }
    return 0;
}

[FJOI2016]神秘数

题面

先忽略其它条件,考虑一个集合 \(s\) 怎么做。若 \(x\) 为当前可以得到的最大值,往 \(s\) 中加入一个数 \(y\),当且仅当 \(y\le x\) 时,\(x\) 会变大。

题目就转化为:

  1. \(ans\) 赋为 \(1\)
  2. 求出区间内值在 \(1-ans\) 内所有数的和 \(res\)
  3. \(ans\) 赋为 \(res+1\)
  4. 重复操作 \(2,3\)

模拟可知最劣情况增量是一个斐波那契数列,最多操作次数在 \(log\) 级别。

用可持久化线段树维护即可。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
    }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
const int N=1e5+10,Lg=31,inf=1e9;
int n,q,cnt,rt[N];
namespace Seg_Tree{
    struct node{
        int ch[2],val;
    }tr[N*Lg];
    #define ls(p) tr[p].ch[0]
    #define rs(p) tr[p].ch[1]
    void update(int &p,int pre,int l,int r,int pos,int val){
        p=++cnt;
        tr[p]=tr[pre];
        tr[p].val+=val;
        if(l==r){
            return;
        }
        int mid=(l+r)>>1;
        if(pos<=mid){
            update(ls(p),ls(pre),l,mid,pos,val);
        }
        else{
            update(rs(p),rs(pre),mid+1,r,pos,val);
        }
    }
    int query(int a,int b,int l,int r,int q_l,int q_r){
        if(q_l<=l&&r<=q_r){
            return tr[a].val-tr[b].val;
        }
        int mid=(l+r)>>1;
        int res=0;
        if(q_l<=mid){
            res+=query(ls(a),ls(b),l,mid,q_l,q_r);
        }
        if(q_r>mid){
            res+=query(rs(a),rs(b),mid+1,r,q_l,q_r);
        }
        return res;
    }
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int n;
    read(n);
    for(int i=1;i<=n;i++){
        int x;
        read(x);
        Seg_Tree::update(rt[i],rt[i-1],1,inf,x,x);
    }
    read(q);
    while(q--){
        int l,r;
        read(l),read(r);
        int ans=1;
        while(1){
            int res=Seg_Tree::query(rt[r],rt[l-1],1,inf,1,ans);
            if(res>=ans){
                ans=res+1;
            }
            else{
                break;
            }
        }
        write_endl(ans);
    }
    return 0;
}

窗口的星星

题面

让我们求一个矩形内最多包含多少个关键点还是太难了,转化一下题意,将一个关键点表示为一个可以包含关键点的矩形,求一个点最多被多少个矩形覆盖。

因为边界是不能包含在内的,所以将所有的格点全部向右平移半格,再向上平移半格,这样包含一个关键点 \((x,y)\) 的矩形就可以表示为 \([x,x+w-1],[y,y+h-1]\)。用扫描线随便维护一下就可以知道一个点最多可以被多少矩形覆盖。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define int long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
            }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
const int N=1e5+10;
int n,w,h,x[N],cntx,cnt;
struct node{
    int xl,xr,y,val;
}mat[N];
bool cmp(node x,node y){
    return x.y==y.y?x.val>y.val:x.y<y.y;
}
int ans[N<<2],tag[N<<2];
int ls(int p){
    return p<<1;
}
int rs(int p){
    return p<<1|1;
}
void push_up(int p){
    ans[p]=max(ans[ls(p)],ans[rs(p)]);
}
void build(int p,int l,int r){
    ans[p]=tag[p]=0;
    if(l==r){
        return;
    }
    int mid=(l+r)>>1;
    build(ls(p),l,mid);
    build(rs(p),mid+1,r);
}
void push_down(int p){
    ans[ls(p)]+=tag[p];
    tag[ls(p)]+=tag[p];
    ans[rs(p)]+=tag[p];
    tag[rs(p)]+=tag[p];
    tag[p]=0;
}
void update(int p,int l,int r,int q_l,int q_r,int val){
    if(q_l<=l&&r<=q_r){
        ans[p]+=val;
        tag[p]+=val;
        return;
    }
    int mid=(l+r)>>1;
    push_down(p);
    if(q_l<=mid){
        update(ls(p),l,mid,q_l,q_r,val);
    }
    if(q_r>mid){
        update(rs(p),mid+1,r,q_l,q_r,val);
    }
    push_up(p);
}
void solve(){
    read(n),read(w),read(h);
    cntx=cnt=0;
    for(int i=1;i<=n;i++){
        int X,Y,val;
        read(X),read(Y),read(val);
        mat[++cnt].xl=X,mat[cnt].xr=X+w-1,mat[cnt].y=Y,mat[cnt].val=val;
        mat[++cnt].xl=X,mat[cnt].xr=X+w-1,mat[cnt].y=Y+h-1,mat[cnt].val=-val;
        x[++cntx]=X,x[++cntx]=X+w-1;
    }
    sort(x+1,x+cntx+1);
    cntx=unique(x+1,x+cntx+1)-x-1;
    sort(mat+1,mat+cnt+1,cmp);
    build(1,1,cntx);
    int Ans=0;
    for(int i=1;i<=cnt;i++){
        mat[i].xl=lower_bound(x+1,x+cntx+1,mat[i].xl)-x;
        mat[i].xr=lower_bound(x+1,x+cntx+1,mat[i].xr)-x;
        update(1,1,cntx,mat[i].xl,mat[i].xr,mat[i].val);
        Ans=max(Ans,ans[1]);
    }
    write_endl(Ans);
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t;
    read(t);
    while(t--){
        solve();
    }
    return 0;
}

火星商店问题

题面

先考虑不新添货物,即没有时间线该怎么做。可以每个点建一棵可持久化01trie,每次从上一个点的01trie上修改。询问时考虑差分,只访问在 \(siz_r-siz_{l-1}>0\) 的点。
根据一个01trie的一个性质,\(\max(ans_{t1},ans_{t2})=ans_{t1\cup t2}\)\(t1,t2,t1\cup t2\) 均代表一棵01trie。
所以可以将询问线段树分治,将修改按照商店的编号排序,并按时间整体二分。对于一个整体二分的时间段 \([l,r]\),此时它对应的修改是按照商店编号有序的,同时对应,可以通过二分得到对一次询问有影响的修改有哪些,在对应区间上的答案可能就是最终答案。又因为一个时间点和一个修改是一一对应的,所以可以选择每次求答案时选择暴力重建可持久化01trie,总复杂度为 \(O(n\log^2n)\)

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pdi pair<double,int>
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
#define eps 1e-9
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
            }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
const int N=1e5+10,Lg=18;
int cnt,n,m,rt[N],ans[N];
vector<int>num[N<<2];
namespace trie{
    struct node{
        int ch[2],cnt;
    }tr[N*(Lg+5)];
    void ins(int &p,int pre,int val){
        p=++cnt;
        int now=p;
        for(int i=Lg;i>=0;i--){
            int opt=val>>i&1;
            tr[now].ch[opt^1]=tr[pre].ch[opt^1];
            tr[now].ch[opt]=++cnt;
            now=tr[now].ch[opt];
            pre=tr[pre].ch[opt];
            tr[now].cnt=tr[pre].cnt+1;
        }
    }
    int query(int l,int r,int val){
        int res=0;
        for(int i=Lg;i>=0;i--){
            int opt=val>>i&1;
            if(tr[tr[r].ch[opt^1]].cnt-tr[tr[l].ch[opt^1]].cnt>0){
                l=tr[l].ch[opt^1];
                r=tr[r].ch[opt^1];
                res+=(1<<i);
            }
            else{
                l=tr[l].ch[opt],r=tr[r].ch[opt];
            }
        }
        return res;
    }
}
struct node{
    int st,ed,timel,timer,x;
}s[N];
struct query{
    int s,v,t;
}q[N],tmp1[N],tmp2[N];
int ls(int p){
    return p<<1;
}
int rs(int p){
    return p<<1|1;
}
void update(int p,int l,int r,int q_l,int q_r,int id){
    if(q_l>q_r){
        return;
    }
    if(q_l<=l&&r<=q_r){
        num[p].pb(id);
        return;
    }
    int mid=(l+r)>>1;
    if(q_l<=mid){
        update(ls(p),l,mid,q_l,q_r,id);
    }
    if(q_r>mid){
        update(rs(p),mid+1,r,q_l,q_r,id);
    }
}
int stk[N],top;
void calc(int p,int l,int r){
    top=cnt=0;
    for(int i=l;i<=r;i++){
        stk[++top]=q[i].s;
        trie::ins(rt[top],rt[top-1],q[i].v);
    }
    for(auto x:num[p]){
        int l=lower_bound(stk+1,stk+top+1,s[x].st)-stk-1,r=upper_bound(stk+1,stk+top+1,s[x].ed)-stk-1;
        ans[x]=max(ans[x],trie::query(rt[l],rt[r],s[x].x));
    }
}
void solve(int p,int l,int r,int L,int R){
    if(L>R){
        return;
    }
    int cnt1=0,cnt2=0;
    calc(p,L,R);
    if(l==r){
        return;
    }
    int mid=(l+r)>>1,idx1=0,idx2=0;
    for(int i=L;i<=R;i++){
        if(q[i].t<=mid){
            tmp1[++idx1]=q[i];
        }
        else{
            tmp2[++idx2]=q[i];
        }
    }
    for(int i=1;i<=idx1;i++){
        q[i+L-1]=tmp1[i];
    }
    for(int i=1;i<=idx2;i++){
        q[i+L-1+idx1]=tmp2[i];
    }
    solve(ls(p),l,mid,L,L+idx1-1);
    solve(rs(p),mid+1,r,L+idx1,R);
}
bool cmp(query x,query y){
    return x.s<y.s;
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    read(n),read(m);
    for(int i=1;i<=n;i++){
        int x;
        read(x);
        trie::ins(rt[i],rt[i-1],x);
    }
    int cnt1=0,cnt2=0;
    for(int i=1;i<=m;i++){
        int type,l,r,x,y;
        read(type);
        if(type==0){
            read(x),read(y);
            q[++cnt1]=(query){x,y,cnt1};
        }
        else{
            read(l),read(r),read(x),read(y);
            ans[++cnt2]=trie::query(rt[l-1],rt[r],x);
            s[cnt2]=(node){l,r,max(1,cnt1-y+1),cnt1,x};
        }
    }
    for(int i=1;i<=cnt2;i++){
        update(1,1,cnt1,s[i].timel,s[i].timer,i);
    }
    sort(q+1,q+cnt1+1,cmp);
    solve(1,1,cnt1,1,cnt1);
    for(int i=1;i<=cnt2;i++){
        write_endl(ans[i]);
    }
    return 0;
}

[SDOI2016]游戏

题面

先考虑树上求距离的公式,令 \(dis_u\) 表示 \(1\)\(u\) 的距离,\(d_{u,v}\) 表示 \(u\)\(v\) 的距离,\(d_{u,v}=dis_u+dis_v-2\times dis_{lca(u,v)}\)。可以发现原路径可以化为从 \(u\)\(\operatorname{lca}(u,v)\)\(\operatorname{lca}(u,v)\)\(v\) 两条路径。这两条路径的方程是不同的,但同一路径上所有点的方程是相同的。
在路径 \(u\)\(\operatorname{lca}(u,v)\) 的点 \(x\)\(u\) 的距离为 \(dis_u-dis_x\),对应的权值方程 \(k\times(dis_u-dis_x)+b\),即 \(k\times dis_u+b-k\times dis_x\),将 \(dis_x\) 看作 \(x\)\(-k\) 看作 \(k\)\(k\times dis_u+b\) 看作 \(b\),这就是路径的权值方程。在另一条路径上的点的权值方程为 \(k\times (dis_u-2\times dis_{lca(u,v)})+b+k\times dis_x\),转成和上述方程一样的形式,可以发现我们将 \(dis_x\) 均视作了 \(x\),可以用李超树维护了,剩下的树剖加李超树维护区间最值。
因为李超树上每个节点存的线段一定包含该区间,所以子树内存的线段的最小值为各线段端点值取 \(\min\)。需要注意的是第 \(0\) 条线段要赋为 \(y=0x+inf\),区间询问时不止要处理分出来的 \(\log\) 个区间,也要记得处理其它有交集的区间标记永久化的线段的答案。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define int long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
    }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
const int N=1e5+10,Lg=20,inf=123456789123456789;
int n,q,fa[N],siz[N],dep[N],dis[N],heavy[N],dfn[N],top[N],rdfn[N],idx;
vector<pii>e[N];
int tot,rt;
struct Line{
    int k,b;
    Line(){
        k=0,b=inf;
    }
}seg[N*2];
namespace Seg_Tree{
    int cnt;
    struct node{
        int ch[2],mn,id;
        node(){
            ch[0]=ch[1]=id=0;
            mn=inf;
        }
    }tr[N*Lg];
    #define ls(p) tr[p].ch[0]
    #define rs(p) tr[p].ch[1]
    int get(int pos,int sid){
        return seg[sid].k*dis[rdfn[pos]]+seg[sid].b;
    }
    void push_up(int p,int l,int r){
        tr[p].mn=min(tr[p].mn,min(get(l,tr[p].id),get(r,tr[p].id)));
        tr[p].mn=min(tr[p].mn,min(tr[ls(p)].mn,tr[rs(p)].mn));
    }
    void change(int &p,int l,int r,int u){
        if(!p){
            p=++cnt;
        }
        int mid=(l+r)>>1,&v=tr[p].id;
        if(get(mid,v)>get(mid,u)){
            swap(u,v);
        }
        if(get(l,v)>get(l,u)){
            change(ls(p),l,mid,u);
        }
        if(get(r,v)>get(r,u)){
            change(rs(p),mid+1,r,u);
        }
        push_up(p,l,r);
    }
    void update(int &p,int l,int r,int q_l,int q_r,int u){
        if(!p){
            p=++cnt;
        }
        if(q_l<=l&&r<=q_r){
            change(p,l,r,u);
            return;
        }
        int mid=(l+r)>>1;
        if(q_l<=mid){
            update(ls(p),l,mid,q_l,q_r,u);
        }
        if(q_r>mid){
            update(rs(p),mid+1,r,q_l,q_r,u);
        }
        push_up(p,l,r);
    }
    int query(int p,int l,int r,int q_l,int q_r){
        if(!p){
            return inf;
        }
        if(q_l<=l&&r<=q_r){
            return tr[p].mn;
        }
        int mid=(l+r)>>1,res=min(get(max(q_l,l),tr[p].id),get(min(q_r,r),tr[p].id));
        if(q_l<=mid){
            res=min(res,query(ls(p),l,mid,q_l,q_r));
        }
        if(q_r>mid){
            res=min(res,query(rs(p),mid+1,r,q_l,q_r));
        }
        return res;
    }
}
void make_tree(int u,int father){
    fa[u]=father;
    siz[u]=1;
    dep[u]=dep[father]+1;
    for(auto x:e[u]){
        int v=x.first,w=x.second;
        if(v==father){
            continue;
        }
        dis[v]=dis[u]+w;
        make_tree(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[heavy[u]]){
            heavy[u]=v;
        }
    }
}
void dfs(int u,int topf){
    top[u]=topf;
    dfn[u]=++idx;
    rdfn[idx]=u;
    if(heavy[u]){
        dfs(heavy[u],topf);
    }
    for(auto x:e[u]){
        int v=x.first;
        if(v==fa[u]||v==heavy[u]){
            continue;
        }
        dfs(v,v);
    }
}
int LCA(int u,int v){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]){
            swap(u,v);
        }
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]){
        swap(u,v);
    }
    return u;
}
void update(int u,int v){   
    while(top[u]!=top[v]){
        Seg_Tree::update(rt,1,n,dfn[top[u]],dfn[u],tot);
        u=fa[top[u]];
    }
    Seg_Tree::update(rt,1,n,dfn[v],dfn[u],tot);
}
int query(int u,int v){
    int res=inf;
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]){
            swap(u,v);
        }
        res=min(res,Seg_Tree::query(rt,1,n,dfn[top[u]],dfn[u]));
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]){
        swap(u,v);
    }
    res=min(res,Seg_Tree::query(rt,1,n,dfn[u],dfn[v]));
    return res;
}
void solve(){
    read(n),read(q);
    for(int i=1,u,v,w;i<n;i++){
        read(u),read(v),read(w);
        e[u].pb(mp(v,w));
        e[v].pb(mp(u,w));
    }
    make_tree(1,0);
    dfs(1,1);
    while(q--){
        int opt,s,t,k,b;
        read(opt),read(s),read(t);
        if(opt==1){
            read(k),read(b);
            int lca=LCA(s,t);
            seg[++tot].k=-k;
            seg[tot].b=b+k*dis[s];
            update(s,lca);
            seg[++tot].k=k;
            seg[tot].b=b+k*(dis[s]-dis[lca]*2);
            update(t,lca);
        }
        else{
            write_endl(query(s,t));
        }
    }
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}

Shortest Path Queries

题面

先考虑不带加边或删边的情况。先求出一条从 \(x\)\(y\) 的路径的异或和 \(val\),显然的是一条路径异或上一个环,就会变成另一条路径。所以我们将所有的环的异或和求出来放入线性基,求出 \(val\) 和这些环异或和的异或最小值在线性基上贪心,如果 \(val\) 从高往低第 \(i\) 位为 \(1\),且线性基第 \(i\) 位有值,那么直接异或。根据线性基的性质,这是一定变小的。

现在有了加边删边操作,因为线性基不方便删除,所以我们可以使用线段树分治将删除转为撤销。又因为要维护环,所以我们还需要可撤销并查集来维护连通性问题。下面仅讨论如何用并查集维护环的异或和。令 \(dis_u\) 表示点 \(u\) 到并查集的根的距离的异或和,当要加入 \((u,v,w)\) 这条边且 \(u,v\) 已经在同一连通块时,这已经是一个环了,环的异或和为 \(dis_u\oplus dis_v\oplus w\),将它插入到线性基中。当 \(u,v\) 分属子树 \(fu\)\(fv\) 时,我们要考虑如何维护 \(dis\) 这个信息。因为要带撤销操作,所以路径压缩是不在我们考虑范围内的,只能考虑按秩合并。假定 \(fu\) 为大的连通块,我们想要 \(u\)\(v\) 的路径异或和为 \(w\),但我们的边只能加在 \(fu\)\(fv\) 之间,列一个等式 \(w_{u,v}=dis_u\oplus dis_v\oplus w_{fu,fv}\),化一下可以得到 \(w_{fu,fv}=w_{u,v}\oplus dis_u\oplus dis_v\),于是我们在 \(fu\)\(fv\) 之间加上一条边权为 \(w_{u,v}\oplus dis_u\oplus dis_v\) 的边。

现在问题来了,对于环套环的情况,这样做是对的吗?显然正确,因为两个环各异或一遍,相当于异或了剩下的一个环,所以我们能够保证所有环都能够拓展原来的路径,也就转化成了我们前面不带加边或删边的情况。

本题还有道类似的题叫做八纵八横

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pb push_back
#define mp make_pair
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
    }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
const int Lg=31,N=4e5+10,inf=1e9;
#define bit bitset<Lg+5>
int n,m,Time[N],tot,q,flag[N];
unordered_map<int,int>id[N];
struct edge{
    int u,v;
    bit w;
}e[N];
struct query{
    int u,v;
}Query[N];
namespace Linear_Basis{
    bit p[Lg+5];
    int stk[N],top;
    void insert(bit x){
        for(int i=Lg;i>=0;i--){
            if(x[i]){
                if(p[i].none()){
                    stk[++top]=i;
                    p[i]=x;
                    return;
                }
                x^=p[i];
            }
        }
    }
    bit query(bit ans){
        for(int i=Lg;i>=0;i--){
            if(ans[i]&&p[i].any()){
                ans^=p[i];
            }
        }
        return ans;
    }
    void del(int pos){
        while(top!=pos){
            p[stk[top--]].reset();
        }
    }
}
namespace dsu{
    int fa[N],siz[N],top;
    bit dis[N];
    struct node{
        int u,v,siz;
    }stk[N];
    void init(){
        for(int i=1;i<=n;i++){
            fa[i]=i;
            siz[i]=1;
        }
    }
    bit get_dis(int u){
        if(fa[u]==u){
            return dis[u];
        }
        return dis[u]^get_dis(fa[u]);
    }
    int getfa(int u){
        if(fa[u]!=u){
            return getfa(fa[u]);
        }
        return u;
    }
    void merge(int u,int v,bit w){
        int fu=getfa(u),fv=getfa(v);
        if(fu==fv){
            Linear_Basis::insert(get_dis(u)^get_dis(v)^w);
            return;
        }
        if(siz[fu]<siz[fv]){
            swap(fu,fv);
        }
        stk[++top]=node{fu,fv,siz[fu]};
        dis[fv]=get_dis(u)^get_dis(v)^w;
        fa[fv]=fu;
        siz[fu]+=siz[fv];
    }
    void del(int pos){
        while(top!=pos){
            fa[stk[top].v]=stk[top].v;
            siz[stk[top].u]=stk[top].siz;
            dis[stk[top].v].reset();
            top--;
        }
    }
}
namespace Seg_Tree{
    vector<int>num[N<<2];
    int ls(int p){
        return p<<1;
    }
    int rs(int p){
        return p<<1|1;
    }
    void update(int p,int l,int r,int q_l,int q_r,int id){
        if(q_l<=l&&r<=q_r){
            num[p].pb(id);
            return;
        }
        int mid=(l+r)>>1;
        if(q_l<=mid){
            update(ls(p),l,mid,q_l,q_r,id);
        }
        if(q_r>mid){
            update(rs(p),mid+1,r,q_l,q_r,id);
        }
    }
    void solve(int p,int l,int r){
        int tag1=Linear_Basis::top,tag2=dsu::top;
        for(auto x:num[p]){
            dsu::merge(e[x].u,e[x].v,e[x].w);
        }
        if(l==r){
            if(flag[l]){
                int u=Query[l].u,v=Query[l].v;
                int ans=Linear_Basis::query(dsu::get_dis(u)^dsu::get_dis(v)).to_ulong();
                write_endl(ans);
            }
        }
        else{
            int mid=(l+r)>>1;
            solve(ls(p),l,mid);
            solve(rs(p),mid+1,r);
        }
        Linear_Basis::del(tag1);
        dsu::del(tag2);
    }
}
void solve(){
    read(n),read(m);
    for(int i=1,u,v,w;i<=m;i++){
        read(u),read(v),read(w);
        e[++tot].u=u;
        e[tot].v=v;
        e[tot].w=w;
        id[v][u]=id[u][v]=tot;
    }
    dsu::init();
    read(q);
    for(int i=1;i<=q;i++){
        int opt,u,v,w;
        read(opt);
        if(opt==1){
            read(u),read(v),read(w);
            e[++tot].u=u;
            e[tot].v=v;
            e[tot].w=w;
            Time[tot]=i;
            id[u][v]=id[v][u]=tot;
        }
        else if(opt==2){
            read(u),read(v);
            int idx=id[u][v];
            Seg_Tree::update(1,0,q,Time[idx],i-1,idx);
            Time[idx]=inf;
        }
        else{
            read(u),read(v);
            Query[i].u=u;
            Query[i].v=v;
            flag[i]=1;
        }
    }
    for(int i=1;i<=tot;i++){
        if(Time[i]<inf){
            Seg_Tree::update(1,0,q,Time[i],q,i);
        }
    }
    Seg_Tree::solve(1,0,q);
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}

[AHOI2013] 连通图

题面

将删边变为加边,找到一条边存在的时间区间,线段树分治后用并查集判连通块数是否为 \(1\) 即可,很板。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
    }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
const int N=2e5+10;
vector<int>num[N<<2],pos[N<<1];
struct edge{
    int u,v;
}e[N<<1];
int n,m,ans[N];
namespace dsu{
    struct node{
        int u,v,siz;
    };
    int siz[N],fa[N],top,flag;
    node stk[N];
    void init(int n){
        for(int i=1;i<=n;i++){
            fa[i]=i;
            siz[i]=1;
        }
        flag=0;
    }
    int getfa(int x){
        if(fa[x]==x){
            return x;
        }
        return getfa(fa[x]);
    }
    void merge(int u,int v){
        int fu=getfa(u),fv=getfa(v);
        if(fu==fv){
            return;
        }
        if(siz[fu]<siz[fv]){
            stk[++top].u=fv;
            stk[top].v=fu;
            stk[top].siz=siz[fv];
            siz[fv]+=siz[fu];
            fa[fu]=fv;
            if(siz[fv]==n){
                flag=1;
            }
        }
        else{
            stk[++top].u=fu;
            stk[top].v=fv;
            stk[top].siz=siz[fu];
            siz[fu]+=siz[fv];
            fa[fv]=fu;
            if(siz[fu]==n){
                flag=1;
            }
        }
    }
    void del(int pos){
        while(top!=pos){
            fa[stk[top].v]=stk[top].v;
            siz[stk[top].u]=stk[top].siz;
            top--;
        }
    }
}
int ls(int p){
    return p<<1;
}
int rs(int p){
    return p<<1|1;
}
void ins(int p,int l,int r,int q_l,int q_r,int id){
    if(q_l<=l&&r<=q_r){
        num[p].pb(id);
        return;
    }
    int mid=(l+r)>>1;
    if(q_l<=mid){
        ins(ls(p),l,mid,q_l,q_r,id);
    }
    if(q_r>mid){
        ins(rs(p),mid+1,r,q_l,q_r,id);
    }
}
void work(int p,int l,int r){
    int pos=dsu::top,flag=dsu::flag;
    for(auto x:num[p]){
        dsu::merge(e[x].u,e[x].v);
    }
    if(l==r){
        if(dsu::flag){
            ans[l]=1;
        }
        else{
            ans[l]=-1;
        }
    }
    else{
        int mid=(l+r)>>1;
        work(ls(p),l,mid);
        work(rs(p),mid+1,r);
    }
    dsu::flag=flag;
    dsu::del(pos);
}
void solve(){
    read(n),read(m);
    dsu::init(n);
    for(int i=1;i<=m;i++){
        read(e[i].u),read(e[i].v);
    }
    int q;
    read(q);
    for(int i=1;i<=q;i++){
        int sum,x;
        read(sum);
        while(sum--){
            read(x);
            pos[x].pb(i);
        }
    }
    for(int i=1;i<=m;i++){
        pos[i].pb(q+1);
        int lst=1;
        for(int j=0;j<pos[i].size();j++){
            if(lst<=pos[i][j]-1){
                ins(1,1,q,lst,pos[i][j]-1,i);
            }
            lst=pos[i][j]+1;
        }
    }
    work(1,1,q);
    for(int i=1;i<=q;i++){
        if(ans[i]==1){
            puts("Connected");
        }
        else if(ans[i]==-1){
            puts("Disconnected");
        }
    }
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}

COT - Count on a tree

题面

树上路径的另一种理解为树上区间,区间求第 \(k\) 小,容易想到主席树。我们可以每个点在父亲的版本上修改,最后答案为 \(u\) 点的版本加上 \(v\) 点的版本减去 \(lca\) 的版本和 \(fa_{lca}\) 的版本的答案。

点击查看代码
#include<bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define pii pair<int,int>
#define pdi pair<double,int>
#define pb push_back
#define eps 1e-9
#define mp make_pair
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T &x){
        x=0;
        int f=1;
        char ch=getchar();
        while(ch>'9'||ch<'0'){
            if(ch=='-'){
                f=-1;
    }
            ch=getchar();
        }
        while(ch>='0'&&ch<='9'){
            x=x*10+(ch-'0');
            ch=getchar();
        }
        x=(f==1?x:-x);
    }
    template<typename T>
    inline void write(T x){
        if(x<0){
            putchar('-');
            x=-x;
        }
        if(x>=10){
            write(x/10);
        }
        putchar(x%10+'0');
    }
    template<typename T>
    inline void write_endl(T x){
        write(x);
        putchar('\n');
    }
    template<typename T>
    inline void write_space(T x){
        write(x);
        putchar(' ');
    }
}
using namespace IO;
const int N=1e5+10,Lg=20;
int val[N],a[N],n,q,m;
namespace Seg_Tree{
    int cnt;
    struct node{
        int ch[2],siz;
    }tr[N*Lg];
    #define ls(p) tr[p].ch[0]
    #define rs(p) tr[p].ch[1]
    void update(int &p,int pre,int l,int r,int pos){
        p=++cnt;
        tr[p]=tr[pre];
        tr[p].siz++;
        if(l==r){
            return;
        }
        int mid=(l+r)>>1;
        if(pos<=mid){
            update(ls(p),ls(pre),l,mid,pos);
        }
        else{
            update(rs(p),rs(pre),mid+1,r,pos);
        }
    }
    int query(int a,int b,int c,int d,int l,int r,int k){
        if(l==r){
            return val[l];
        }
        int x=tr[ls(a)].siz+tr[ls(b)].siz-tr[ls(c)].siz-tr[ls(d)].siz,mid=(l+r)>>1;
        if(x>=k){
            return query(ls(a),ls(b),ls(c),ls(d),l,mid,k);
        }
        else{
            return query(rs(a),rs(b),rs(c),rs(d),mid+1,r,k-x);
        }
    }
}
int head[N],tot;
int fa[N],siz[N],dep[N],heavy[N],rt[N],top[N];
struct edge{
    int v,nxt;
}e[N<<1];
void add(int u,int v){
    e[++tot].v=v;
    e[tot].nxt=head[u];
    head[u]=tot;
}
void add_e(int u,int v){
    add(u,v);
    add(v,u);
}
void make_tree(int u,int father){
    Seg_Tree::update(rt[u],rt[father],1,m,a[u]);
    fa[u]=father;
    siz[u]=1;
    dep[u]=dep[fa[u]]+1;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(v==father){
            continue;
        }
        make_tree(v,u);
        siz[u]+=siz[v];
        if(siz[v]>siz[heavy[u]]){
            heavy[u]=v;
        }
    }
}
void dfs(int u,int topf){
    top[u]=topf;
    if(heavy[u]){
        dfs(heavy[u],topf);
    }
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].v;
        if(v==heavy[u]||v==fa[u]){
            continue;
        }
        dfs(v,v);
    }
}
int LCA(int u,int v){
    while(top[u]!=top[v]){
        if(dep[top[u]]<dep[top[v]]){
            swap(u,v);
        }
        u=fa[top[u]];
    }
    if(dep[u]>dep[v]){
        swap(u,v);
    }
    return u;
}
void solve(){
    read(n),read(q);
    for(int i=1;i<=n;i++){
        read(a[i]);
        val[i]=a[i];
    }
    for(int i=1;i<n;i++){
        int u,v;
        read(u),read(v);
        add_e(u,v);
    }
    sort(val+1,val+n+1);
    m=unique(val+1,val+n+1)-val-1;
    for(int i=1;i<=n;i++){
        a[i]=lower_bound(val+1,val+m+1,a[i])-val;
    }
    make_tree(1,0);
    dfs(1,1);
    while(q--){
        int u,v,k,lca;
        read(u),read(v),read(k);
        lca=LCA(u,v);
        int ans=Seg_Tree::query(rt[u],rt[v],rt[lca],rt[fa[lca]],1,m,k);
        write_endl(ans);
    }
}
signed main(){
    #ifndef ONLINE_JUDGE
        freopen("1.in","r",stdin);
        freopen("1.out","w",stdout);
    #endif
    int t=1;
    while(t--){
        solve();
    }
    return 0;
}
posted @ 2023-03-25 12:09  luo_shen  阅读(40)  评论(0)    收藏  举报