202512/202601集训部分做题记录

20251216

A - 软件包管理器

树剖板子。

代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
vector<int> to[100005];
int siz[100005],dep[100005],hs[100005],p[100005];
void dfs1(int x=1,int fa=0){
    siz[x]=1,dep[x]=dep[fa]+1;p[x]=fa;
    for(auto ed:to[x]){
        if(ed==fa)continue;
        dfs1(ed,x);
        siz[x]+=siz[ed];
        if(!hs[x]||siz[hs[x]]<siz[ed])hs[x]=ed;
    }
}
int dfn[100005],edn[100005],tp[100005],cnt;
void dfs2(int x=1,int fa=0,int top=1){
    dfn[x]=++cnt;tp[x]=top;
    if(hs[x])dfs2(hs[x],x,top);
    for(auto ed:to[x]){
        if(ed==fa||ed==hs[x])continue;
        dfs2(ed,x,ed);
    }
    edn[x]=cnt;
}
int v[400005],tg[400005];
void pushup(int x){v[x]=v[x<<1]+v[x<<1|1];}
void pushdn(int x,int l,int r){
    if(tg[x]==-1)return;
    int mid=(l+r)>>1;
    v[x<<1]=(mid-l+1)*tg[x],tg[x<<1]=tg[x];
    v[x<<1|1]=(r-mid)*tg[x],tg[x<<1|1]=tg[x];
    tg[x]=-1;
}
void chg(int L,int R,int op,int x=1,int l=1,int r=n){
    if(L>R)return;
    if(L<=l&&r<=R)return void((v[x]=(r-l+1)*op,tg[x]=op));
    pushdn(x,l,r);
    int mid=(l+r)>>1;
    if(L<=mid)chg(L,R,op,x<<1,l,mid);
    if(mid<R)chg(L,R,op,x<<1|1,mid+1,r);
    pushup(x);
}
int que(int L,int R,int x=1,int l=1,int r=n){
    if(L>R)return 0;
    if(L<=l&&r<=R)return v[x];
    pushdn(x,l,r);
    int mid=(l+r)>>1,res=0;
    if(L<=mid)res+=que(L,R,x<<1,l,mid);
    if(mid<R)res+=que(L,R,x<<1|1,mid+1,r);
    return res;
}
int Set(int x){
    int res=0;
    while(x){
        int tmp=que(dfn[tp[x]],dfn[x]);
        res+=dfn[x]-dfn[tp[x]]+1-tmp;
        chg(dfn[tp[x]],dfn[x],1);
        x=p[tp[x]];
    }
    return res;
}
int rSet(int x){
    int res=que(dfn[x],edn[x]);
    chg(dfn[x],edn[x],0);
    return res;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    fill(tg,tg+400000,-1);
    cin>>n;
    for(int i=2;i<=n;i++){
        int u;
        cin>>u;u++;
        to[u].push_back(i);
        to[i].push_back(u);
    }
    dfs1();dfs2();
    cin>>q;
    while(q--){
        string op;int x;
        cin>>op>>x;x++;
        if(op[0]=='i')cout<<Set(x)<<'\n';
        else cout<<rSet(x)<<'\n';
    }
}

B - 保卫王国

想扩展到更大的子树,需要的信息只有根的颜色,所以很容易设计一个 DP。

然后是经典的树剖优化树上 DP。此类题目主要要分开轻重边转移,然后进一步的处理。

本题是带修,所以考虑重链剖分+线段树维护 DDP 即可。

代码
#include<bits/stdc++.h>
#define int long long
const int inf=1e16;
using namespace std;
int n,q,w[100005];
string typ;
vector<int> to[100005];
int siz[100005],dep[100005],hs[100005],p[100005];
void dfs1(int x=1,int fa=0){
    siz[x]=1,dep[x]=dep[fa]+1;p[x]=fa;
    for(auto ed:to[x]){
        if(ed==fa)continue;
        dfs1(ed,x);
        siz[x]+=siz[ed];
        if(!hs[x]||siz[hs[x]]<siz[ed])hs[x]=ed;
    }
}
int dfn[100005],edn[100005],tp[100005],dn[100005],cnt;
void dfs2(int x=1,int fa=0,int top=1){
    dfn[x]=++cnt;tp[x]=top;dn[top]=x;
    if(hs[x])dfs2(hs[x],x,top);
    for(auto ed:to[x]){
        if(ed==fa||ed==hs[x])continue;
        dfs2(ed,x,ed);
    }
    edn[x]=cnt;
}
struct mat{
    int a[2][2];
    mat(int x=inf){for(int i=0;i<2;i++)for(int j=0;j<2;j++)a[i][j]=(i==j?x:inf);}
    int *operator[](int x){return a[x];}
};
mat operator*(mat a,mat b){
    mat r;
    r[0][0]=min(a[0][0]+b[0][0],a[0][1]+b[1][0]);
    r[0][1]=min(a[0][0]+b[0][1],a[0][1]+b[1][1]);
    r[1][0]=min(a[1][0]+b[0][0],a[1][1]+b[1][0]);
    r[1][1]=min(a[1][0]+b[0][1],a[1][1]+b[1][1]);
    return r;
}
mat v[400005];
int f[100005][2],g[100005][2];
void chg(int pos,mat vl,int x=1,int l=1,int r=n){
    if(l==r)return void(v[x]=vl);
    int mid=(l+r)>>1;
    if(pos<=mid)chg(pos,vl,x<<1,l,mid);
    else chg(pos,vl,x<<1|1,mid+1,r);
    v[x]=v[x<<1|1]*v[x<<1];
}
mat que(int L,int R,int x=1,int l=1,int r=n){
    if(L>R)return inf;
    if(L<=l&&r<=R)return v[x];
    int mid=(l+r)>>1;mat res=0;
    if(mid<R)res=res*que(L,R,x<<1|1,mid+1,r);
    if(L<=mid)res=res*que(L,R,x<<1,l,mid);
    return res;
}
void build(int x=1,int fa=0){
    for(auto ed:to[x]){
        if(ed==fa)continue;
        build(ed,x);
        f[x][0]+=f[ed][1];
        f[x][1]+=min(f[ed][0],f[ed][1]);
    }
    f[x][1]+=w[x];
    for(auto ed:to[x]){
        if(ed==fa||ed==hs[x])continue;
        g[x][0]+=min(f[ed][0],f[ed][1]);
        g[x][1]+=f[ed][1];
    }
    mat tpm;
    tpm[0][0]=inf;tpm[1][0]=g[x][1];tpm[0][1]=tpm[1][1]=g[x][0]+w[x];
    chg(dfn[x],tpm);
}
set<pair<int,int> > conr;
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>q>>typ;
    for(int i=1;i<=n;i++)cin>>w[i];
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;conr.insert({u,v});conr.insert({v,u});
        to[u].push_back(v);
        to[v].push_back(u);
    }
    dfs1();dfs2();build();
    while(q--){
        int a,x,b,y;
        cin>>a>>x>>b>>y;
        if(x==0&&y==0&&conr.find({a,b})!=conr.end()){cout<<"-1\n";continue;}
        auto change=[](int a,int x){
            if(x==0){
                mat tpm=inf;tpm[1][0]=g[a][1];
                chg(dfn[a],tpm);
            }
            else if(x==1){
                mat tpm=inf;tpm[0][1]=tpm[1][1]=g[a][0]+w[a];
                chg(dfn[a],tpm);
            }
            else{
                mat tpm=inf;tpm[1][0]=g[a][1];tpm[0][1]=tpm[1][1]=g[a][0]+w[a];
                chg(dfn[a],tpm);
            }
            a=tp[a];
            while(p[a]){
                mat trs=que(dfn[a],dfn[dn[a]]);
                int f0=min(trs[0][0],trs[1][0]),f1=min(trs[0][1],trs[1][1]);
                g[p[a]][0]-=min(f[a][0],f[a][1]);
                g[p[a]][1]-=f[a][1];
                f[a][0]=f0,f[a][1]=f1;
                g[p[a]][0]+=min(f[a][0],f[a][1]);
                g[p[a]][1]+=f[a][1];
                mat tpm=inf;tpm[1][0]=g[p[a]][1];tpm[0][1]=tpm[1][1]=g[p[a]][0]+w[p[a]];
                chg(dfn[p[a]],tpm);
                a=tp[p[a]];
            }
            mat trs=que(dfn[a],dfn[dn[a]]);
            int f0=min(trs[0][0],trs[1][0]),f1=min(trs[0][1],trs[1][1]);
            f[a][0]=f0,f[a][1]=f1;
        };
        change(a,x);
        change(b,y);
        int ans=min(f[1][0],f[1][1]);
        cout<<ans<<'\n';
        change(b,2);
        change(a,2);
        
    }
}

C - 楼房重建

显然,我们希望维护类似斜率的单调栈信息。由于带修,所以我们还得维护区间信息。

但是我们不能维护出很多栈的具体元素值。

一个思路是暴力数据结构比如分块。分块的好处是个数少,可以维护出具体的栈信息。

我们不妨直接按照 \(B\) 对序列进行分块。单点修改就直接对所在块暴力重构,查询考虑从左到右扫描块,维护已处理部分的栈顶(最大值),每次新扫到一个块就二分一下要被删除的部分。这样修改是 \(O(B)\),查询是 \(O(\frac{n}{B}\log{n})\),取 \(B=\sqrt{n\log{n}}\) 做到 \(O(q\sqrt{n\log{n}})\)

另一个思路是线段树,需要用到一个递归 pushup 的技巧。

具体来说,我们在线段树每个节点上只维护对应栈的大小和栈顶,剩下的部分则交给递归结构。

然后考虑这玩意怎么 pushup。首先左半部分的栈结构不会改变,主要是右半部分的结构。此时我们继续分治右半区间,如果我们可以通过讨论使得我们只用向其中一侧递归那么 pushup 的复杂度就可以做到 \(O(\log^2{n})\)

考虑有可能左半部分完全被弹掉了。即左半部分对应栈的栈顶小了,可以直接向右半部分递归。

否则左半部分有保留的点。那么对于右半部分,只用考虑左半部分对其的影响,贡献就是 \(len_{当前区间}-len_{左半部分}\)。然后向左递归。

然后就没了,\(O(q\log^2{n})\)

代码 q(logn)^2
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,h[100005];
int mxp[400005],len[400005];
void build(int x=1,int l=1,int r=n){
    mxp[x]=l;len[x]=1;
    if(l==r)return;
    int mid=(l+r)>>1;
    build(x<<1,l,mid);build(x<<1|1,mid+1,r);
}
void pushup(int x,int l,int r){
    int mid=(l+r)>>1;
    mxp[x]=mxp[x<<1];len[x]=len[x<<1];
    int xx=x<<1|1,ll=mid+1,rr=r,rem=mxp[x];
    while(1){
        if(ll==rr){
            if(h[mxp[x]]*mxp[xx]<h[mxp[xx]]*mxp[x]){
                len[x]++;
                if(h[rem]*mxp[xx]<h[mxp[xx]]*rem)rem=mxp[xx];
            }
            break;
        }
        int mmid=(ll+rr)>>1;
        if(h[mxp[x]]*mxp[xx<<1]>=h[mxp[xx<<1]]*mxp[x])xx=xx<<1|1,ll=mmid+1;
        else{
            len[x]+=len[xx]-len[xx<<1];
            if(len[xx]-len[xx<<1]&&h[rem]*mxp[xx<<1|1]<h[mxp[xx<<1|1]]*rem)rem=mxp[xx<<1|1];
            xx=xx<<1;rr=mmid;
        }
    }
    mxp[x]=rem;
}
void chg(int pos,int y,int x=1,int l=1,int r=n){
    if(l==r)return void(h[pos]=y);
    int mid=(l+r)>>1;
    if(pos<=mid)chg(pos,y,x<<1,l,mid);
    else chg(pos,y,x<<1|1,mid+1,r);
    pushup(x,l,r);
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    build();
    while(m--){
        int x,y;
        cin>>x>>y;
        chg(x,y);
        cout<<len[1]-(h[1]==0)<<'\n';
    }
}

D - 火车管理

就是还是线段树维护区间答案,叶子处维护栈还有正常节点维护 push tag 的话就用平衡树。

pushdn 就是平衡树合并。但是这样会点数爆炸,就可持久化一下,新建的点数就是 log 了。

这样得到一个时空均为 \(O(n\log^2{n})\) 的做法。我写了一发 70pts。

代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e5;
int n,m,ty;
namespace DS{
    //bst
    mt19937 rnd(time(0));
    struct node{int v,hv,ls,rs,sz;}tn[N*100+5];
    int cnt;
    #define ls(x) tn[x].ls
    #define rs(x) tn[x].rs
    void pushup(int x){
        tn[x].sz=tn[ls(x)].sz+tn[rs(x)].sz+1;
    }
    void spiltsz(int rt,int sz,int &x,int &y){
        if(!rt)return void(x=y=0);
        int nd=++cnt;tn[nd]=tn[rt];
        if(tn[ls(rt)].sz+1<=sz)x=nd,spiltsz(rs(rt),sz-(tn[ls(rt)].sz+1),rs(x),y);
        else y=nd,spiltsz(ls(rt),sz,x,ls(y));
        pushup(nd);
    }
    int merge(int x,int y){
        if(!x||!y)return (cnt++,tn[cnt]=tn[x+y],cnt);
        int nd=++cnt;
        if(tn[x].hv<tn[y].hv)return (tn[nd]=tn[x],rs(nd)=merge(rs(x),y),pushup(nd),nd);
        else return (tn[nd]=tn[y],ls(nd)=merge(x,ls(y)),pushup(nd),nd);
    }
    int maxn(int x){
        if(!rs(x))return tn[x].v;
        return maxn(rs(x));
    }
    //sgt
    int v[(N<<2)+5],tg[(N<<2)+5];
    void exc(int x,int l,int r,int nd){
        tg[x]=merge(tg[x],nd);
        v[x]=maxn(nd)*(r-l+1);
    }
    void pushdn(int x,int l,int r){
        if(!tg[x])return;
        int mid=(l+r)>>1;
        exc(x<<1,l,mid,tg[x]);
        exc(x<<1|1,mid+1,r,tg[x]);
        tg[x]=0;
    }
    void push(int L,int R,int vl,int x=1,int l=1,int r=n){
        if(L>R)return;
        if(L<=l&&r<=R)return exc(x,l,r,(cnt++,tn[cnt]={vl,(int)rnd(),0,0,1},cnt));
        pushdn(x,l,r);
        int mid=(l+r)>>1;
        if(L<=mid)push(L,R,vl,x<<1,l,mid);
        if(mid<R)push(L,R,vl,x<<1|1,mid+1,r);
        v[x]=v[x<<1]+v[x<<1|1];
    }
    void pop(int pos,int x=1,int l=1,int r=n){
        if(l==r){
            if(!tg[x])return;
            int sz=tn[tg[x]].sz,sv,er;
            spiltsz(tg[x],sz-1,sv,er);
            tg[x]=sv;
            v[x]=maxn(tg[x]);
            return;
        }
        pushdn(x,l,r);
        int mid=(l+r)>>1;
        if(pos<=mid)pop(pos,x<<1,l,mid);
        else pop(pos,x<<1|1,mid+1,r);
        v[x]=v[x<<1]+v[x<<1|1];
    }
    int que(int L,int R,int x=1,int l=1,int r=n){
        if(L>R)return 0;
        if(L<=l&&r<=R)return v[x];
        pushdn(x,l,r);
        int mid=(l+r)>>1,res=0;
        if(L<=mid)res+=que(L,R,x<<1,l,mid);
        if(mid<R)res+=que(L,R,x<<1|1,mid+1,r);
        return res;
    }
}using namespace DS;
int lans;
signed main(){
    // freopen("d/C3.in","r",stdin);
    // freopen("trashbin.out","w",stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>ty;
    while(m--){
        int op;
        cin>>op;
        if(op==1){
            int l,r;
            cin>>l>>r;
            l=(l+lans*ty)%n+1;
            r=(r+lans*ty)%n+1;
            if(l>r)swap(l,r);
            cout<<(lans=que(l,r))<<'\n';
        }
        else if(op==2){
            int l;
            cin>>l;
            l=(l+lans*ty)%n+1;
            pop(l);
        }
        else{
            int l,r,x;
            cin>>l>>r>>x;
            l=(l+lans*ty)%n+1;
            r=(r+lans*ty)%n+1;
            if(l>r)swap(l,r);
            push(l,r,x);
        }
    }
}

说实话代码不算太长,得分也不低,不失为一种得分手段。

考虑 100 pts 咋做。

考虑可持久化线段树,维护当前栈顶和插入时间。

那么 push 就是区间修改。pop 就是找到前面的那个版本,把信息复制过来,答案的话另外用一颗线段树维护就好。

调了很久,主要是我的可持久化线段树区间修改的设计比较奇怪。

代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5;
int n,m,ty;
namespace DS{
    ll v[(N<<2)+5],tg[(N<<2)+5];
    void chg(int L,int R,int vl,int x=1,int l=1,int r=n){
        if(L>R)return;
        if(L<=l&&r<=R)return void((v[x]=(r-l+1)*vl,tg[x]=vl));
        int mid=(l+r)>>1;
        if(tg[x]!=-1){
            v[x<<1]=(mid-l+1)*tg[x],v[x<<1|1]=(r-mid)*tg[x];
            tg[x<<1]=tg[x<<1|1]=tg[x];
            tg[x]=-1;
        }
        if(L<=mid)chg(L,R,vl,x<<1,l,mid);
        if(mid<R)chg(L,R,vl,x<<1|1,mid+1,r);
        v[x]=v[x<<1]+v[x<<1|1];
    }
    ll que(int L,int R,int x=1,int l=1,int r=n){
        if(L>R)return 0;
        if(L<=l&&r<=R)return v[x];
        int mid=(l+r)>>1;
        if(tg[x]!=-1){
            v[x<<1]=(mid-l+1)*tg[x],v[x<<1|1]=(r-mid)*tg[x];
            tg[x<<1]=tg[x<<1|1]=tg[x];
            tg[x]=-1;
        }
        ll res=0;
        if(L<=mid)res+=que(L,R,x<<1,l,mid);
        if(mid<R)res+=que(L,R,x<<1|1,mid+1,r);
        return res;
    }
    int tim[N*100+5],ls[N*100+5],rs[N*100+5],cnt,gt;
    int hv[N*100+5];
    int gf;
    void push(int L,int R,int vl,int ft,int rtl,int &rtr,int l=1,int r=n){
        if(L>R)return;
        rtr=++cnt;
        if(rtl){tim[rtr]=tim[rtl];hv[rtr]=hv[rtl];ls[rtr]=ls[rtl];rs[rtr]=rs[rtl];}
        else{
            tim[rtr]=tim[gf];hv[rtr]=hv[gf];ls[rtr]=rs[rtr]=0;
        }
        if(L<=l&&r<=R){
            tim[rtr]=ft;hv[rtr]=vl;ls[rtr]=rs[rtr]=0;
            return;
        }
        int mid=(l+r)>>1;
        gf=rtr;
        if(L<=mid)push(L,R,vl,ft,ls[rtl],ls[rtr],l,mid);
        gf=rtr;
        if(mid<R)push(L,R,vl,ft,rs[rtl],rs[rtr],mid+1,r);
    }
    int rt[N+5];
    #define Push(l,r,x) do{gt++;push(l,r,x,gt,rt[gt-1],rt[gt]);chg(l,r,x);}while(0)
    pair<int,int> quer(int pos,int rt,int l=1,int r=n){
        if(!rt)return make_pair(0,0);
        if(l==r)return make_pair(tim[rt],hv[rt]);
        int mid=(l+r)>>1;
        if(pos<=mid){
            if(!ls[rt])return make_pair(tim[rt],hv[rt]);
            return quer(pos,ls[rt],l,mid);
        }
        else{
            if(!rs[rt])return make_pair(tim[rt],hv[rt]);
            return quer(pos,rs[rt],mid+1,r);
        }
    }
    void pop(int pos){
        int sd=quer(pos,rt[gt]).first;
        if(!sd)return;
        pair<int,int> tmp=quer(pos,rt[sd-1]);
        gt++;push(pos,pos,tmp.second,tmp.first,rt[gt-1],rt[gt]);chg(pos,pos,tmp.second);
    }
}using namespace DS;
ll lans;
signed main(){
    // freopen("d/C3.in","r",stdin);
    // freopen("trashbin.out","w",stdout);
    fill(tg,tg+(N<<2)+1,-1);
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>ty;
    while(m--){
        int op;
        cin>>op;
        if(op==1){
            int l,r;
            cin>>l>>r;
            l=(l+lans*ty)%n+1;
            r=(r+lans*ty)%n+1;
            if(l>r)swap(l,r);
            cout<<(lans=que(l,r))<<'\n';
        }
        else if(op==2){
            int l;
            cin>>l;
            l=(l+lans*ty)%n+1;
            pop(l);
        }
        else{
            int l,r,x;
            cin>>l>>r>>x;
            l=(l+lans*ty)%n+1;
            r=(r+lans*ty)%n+1;
            if(l>r)swap(l,r);
            Push(l,r,x);
        }
    }
}

E - CPU 监控

历史最值板子。本质是维护向量 \((1,now,his)\),修改可以使用矩阵刻画。

对矩阵讨论后不难发现变化的只有 4 个位置。

代码
#include<bits/stdc++.h>
#define ll long long
const ll inf=1e16;
using namespace std;
int n;
struct mat{
    ll a,b,c,d;
    mat(ll a=-inf,ll b=-inf,ll c=-inf,ll d=-inf):a(a),b(b),c(c),d(d){}
};
mat adj(mat x){
    x.a=max(x.a,-inf);
    x.b=max(x.b,-inf);
    x.c=max(x.c,-inf);
    x.d=max(x.d,-inf);
    return x;
}
mat operator+(mat x,mat y){x.a=max(x.a,y.a);x.b=max(x.b,y.b);x.c=max(x.c,y.c);x.d=max(x.d,y.d);return adj(x);}
mat operator*(mat x,mat y){mat r;r.a=max(y.a,x.a+y.c);r.b=max({x.b,y.b,x.a+y.d});r.c=x.c+y.c;r.d=max(x.c+y.d,x.d);return adj(r);}
#define Emat mat(-inf,-inf,0,-inf)
mat v[400005],tg[400005];
void pushup(int x){v[x]=v[x<<1]+v[x<<1|1];}
void pushdn(int x){
    v[x<<1]=v[x<<1]*tg[x];tg[x<<1]=tg[x<<1]*tg[x];
    v[x<<1|1]=v[x<<1|1]*tg[x];tg[x<<1|1]=tg[x<<1|1]*tg[x];
    tg[x]=Emat;
}
void chg(int L,int R,mat vl,int x=1,int l=1,int r=n){
    if(L>R)return;
    if(L<=l&&r<=R)return void((v[x]=v[x]*vl,tg[x]=tg[x]*vl));
    pushdn(x);
    int mid=(l+r)>>1;
    if(L<=mid)chg(L,R,vl,x<<1,l,mid);
    if(mid<R)chg(L,R,vl,x<<1|1,mid+1,r);
    pushup(x);
}
mat que(int L,int R,int x=1,int l=1,int r=n){
    if(L>R)return mat();
    if(L<=l&&r<=R)return v[x];
    pushdn(x);
    int mid=(l+r)>>1;
    mat res=mat();
    if(L<=mid)res=res+que(L,R,x<<1,l,mid);
    if(mid<R)res=res+que(L,R,x<<1|1,mid+1,r);
    return res;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        int st;
        cin>>st;
        chg(i,i,mat(st,st,-inf,-inf));
    }
    int q;
    cin>>q;
    while(q--){
        char op;int l,r;
        cin>>op>>l>>r;
        if(op=='Q')cout<<que(l,r).a<<'\n';
        if(op=='A')cout<<que(l,r).b<<'\n';
        if(op=='P'){
            int x;
            cin>>x;
            chg(l,r,mat(-inf,-inf,x,x));
        }
        if(op=='C'){
            int x;
            cin>>x;
            chg(l,r,mat(x,x,-inf,-inf));
        }
    }
}

G - 切树游戏

单点修改,查询连通块异或和为 \(k\) 的数量。

首先考虑询问咋做。其实感觉除了 DP 没啥思路。DP 就是一个简单的树形 DP,记 \(f_{i,j}\) 表示 \(i\) 子树中异或和为 \(j\) 的包含 \(i\) 的连通块个数。转移直接暴力就是 \(O(nm^2)\),结合 FWT 不难做到 \(O(nm\log{m})\) 之类的复杂度。

由于每个点只有一个值,这个不难手动 FWT,转移一直保持点值状态就是 \(O(nm)\) 的了。根据 xor FWT 的位矩阵可以知道 \(a_i=(-1)^{i \wedge v}\)\(\wedge\) 是按位与。

还有个修改,又是 DP,很难不往 DDP 上想。考虑树剖能否对先前的转移模式进行优化。由于过程一直是点值状态所以第二维间是独立的。

我们发现转移相当简单,就是加和乘两种线性运算(集合幂级数乘上 \((1+F)\)\(1\) 像前面一样手动 FWT 后累加上去就好),于是容易挂上树剖去做。维护重链顶的 DP 结果和轻边过来的系数就好。

那么修改就可以做到 \(O(m\log{n})\),非常优秀。

求解随便暴力逆运算。

最终就是 \(O(nm+qm\log{nm})\)

补充一些具体细节

首先我们要维护这样一些东西:当前根的答案多项式(集合幂级数)\(f_u\) 和子树内所有点的答案多项式之和 \(g_u\)

区分轻重边转移,我们先将轻边转移叠过来,即求出 \(h_u=\prod_{v \in son_u \wedge v \neq hs_u}(1+f_v)\)\(l_u=\sum_{v \in son_u \wedge v \neq hs_u}g_v\)

那么转移就可以具体表示为:

\[f_u = x^{v_u}h_u(1+f_{hs_u})\\ g_u = l_u+g_{hs_u}+f_u \]

都可以预先使用 FWT,于是每一位就分离开变成单独的问题了。对于其中的一位,转移就是(\(1\) 的 xor FWT 是每一位都是 \(1\),假设这一位是 \(c\)\(x^{v_u}\) 就是 \((-1)^{c \wedge v_u}\)):

\[f'_u = (-1)^{c \wedge v_u}h'_u(1+f'_{hs_u})\\ g'_u = l'_u+g'_{hs_u}+f'_u \]

对着式子可以知道维护三维向量 \((1,f'_u,g'_u)\) 的转移矩阵即可。

然后写代码的时候发现还有 \(h\) 的乘法撤销问题。这个很简单就是把 \(0\) 视为级数 \(x\) 就好,这应该是算经典技巧了。只用对 \(h\) 做,因为其他的要么好撤销要么不用撤销。

感觉挺简单的,可能是因为前面预先想到了 FWT 导致后面契合上 DDP 就很简单,如果没想到 FWT 后面的 DDP 可能就想不出来了。所以可以知道 DFT 之类的另一个伟大作用就是独立各个位。

代码
#include<bits/stdc++.h>
const int mod=10007;
using namespace std;
int inv[mod],pc[128];
inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);}
inline void sub(int &x,int y){(x-=y)<0&&(x+=mod);}
inline int pls(int x,int y){return (x+y>=mod?x+y-mod:x+y);}
inline int subb(int x,int y){return (x<y?x+mod-y:x-y);}
struct mat{
    int a,b,c,d;
    mat(int a=0,int b=0,int c=0,int d=0):a(a),b(b),c(c),d(d){}
};
mat operator*(mat x,mat y){
    mat r;
    r.a=pls(1ll*x.a*y.c%mod,y.a);
    r.b=pls(pls(x.b,y.b),1ll*x.a*y.d%mod);
    r.c=1ll*x.c*y.c%mod;
    r.d=pls(1ll*x.c*y.d%mod,x.d);
    return r;
}
int n,m,v[30005];
vector<int> to[30005];
int sz[30005],hs[30005],dep[30005],p[30005];
void dfs1(int x=1,int fa=0){
    sz[x]=1;dep[x]=dep[fa]+1;p[x]=fa;
    for(auto ed:to[x]){
        if(ed==fa)continue;
        dfs1(ed,x);sz[x]+=sz[ed];
        if(!hs[x]||sz[hs[x]]<sz[ed])hs[x]=ed;
    }
}
int dfn[30005],cnt,tp[30005],dn[30005];
void dfs2(int x=1,int fa=0,int top=1){
    dfn[x]=++cnt;tp[x]=top;dn[top]=x;
    if(hs[x])dfs2(hs[x],x,top);
    for(auto ed:to[x]){
        if(ed==fa||ed==hs[x])continue;
        dfs2(ed,x,ed);
    }
}
int f[30005][128],g[30005][128],h[30005][128][2],l[30005][128];
#define Val(arr) (arr[1]>0?0:arr[0]) 
mat vv[128][120005];
void chg(int pos,mat vl,int id,int x=1,int l=1,int r=n){
    if(l==r)return void(vv[id][x]=vl);
    int mid=(l+r)>>1;
    if(pos<=mid)chg(pos,vl,id,x<<1,l,mid);
    else chg(pos,vl,id,x<<1|1,mid+1,r);
    vv[id][x]=vv[id][x<<1|1]*vv[id][x<<1];
}
mat que(int L,int R,int id,int x=1,int l=1,int r=n){
    if(L>R)return 0;
    if(L<=l&&r<=R)return vv[id][x];
    int mid=(l+r)>>1;mat res=mat(0,0,1,0);
    if(mid<R)res=res*que(L,R,id,x<<1|1,mid+1,r);
    if(L<=mid)res=res*que(L,R,id,x<<1,l,mid);
    return res;
}
mat budmat(int x,int c){
    int vl=0;
    if(pc[c&v[x]]&1)sub(vl,Val(h[x][c]));
    else add(vl,Val(h[x][c]));
    mat r(vl,vl,vl,vl);
    add(r.b,l[x][c]);
    return r;
}
void build(int x=1,int fa=0){
    for(int i=0;i<m;i++)
        f[x][i]=((pc[v[x]&i]&1)?mod-1:1),
        h[x][i][0]=1,
        l[x][i]=h[x][i][1]=0;
    if(hs[x]){
        build(hs[x],x);
        for(int i=0;i<m;i++)
            f[x][i]=1ll*pls(f[hs[x]][i],1)*f[x][i]%mod,
            g[x][i]=g[hs[x]][i];
    }
    for(auto ed:to[x]){
        if(ed==fa||ed==hs[x])continue;
        build(ed,x);
        for(int i=0;i<m;i++){
            int vl=pls(1,f[ed][i]);
            if(!vl)h[x][i][1]++;
            else h[x][i][0]=1ll*vl*h[x][i][0]%mod;
            add(l[x][i],g[ed][i]);
        }
    }
    for(int i=0;i<m;i++)
        f[x][i]=1ll*f[x][i]*Val(h[x][i])%mod,
        add(g[x][i],f[x][i]),
        add(g[x][i],l[x][i]),
        chg(dfn[x],budmat(x,i),i);
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    inv[1]=1;
    for(int i=2;i<mod;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    for(int i=1;i<128;i++)pc[i]=pc[i>>1]+(i&1);
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>v[i];
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        to[u].push_back(v);
        to[v].push_back(u);
    }
    dfs1();dfs2();build();
    int q;
    cin>>q;
    while(q--){
        string op;int x,y;
        cin>>op>>x;
        if(op[0]=='C'){
            cin>>y;
            v[x]=y;
            for(int i=0;i<m;i++)chg(dfn[x],budmat(x,i),i);
            x=tp[x];
            while(p[x]){
                for(int i=0;i<m;i++){
                    mat rec=que(dfn[x],dfn[dn[x]],i);
                    int vl=pls(1,f[x][i]);
                    if(!vl)h[p[x]][i][1]--;
                    else h[p[x]][i][0]=1ll*inv[vl]*h[p[x]][i][0]%mod;
                    sub(l[p[x]][i],g[x][i]);
                    f[x][i]=rec.a;
                    g[x][i]=rec.b;
                    vl=pls(1,f[x][i]);
                    if(!vl)h[p[x]][i][1]++;
                    else h[p[x]][i][0]=1ll*vl*h[p[x]][i][0]%mod;
                    add(l[p[x]][i],g[x][i]);
                    chg(dfn[p[x]],budmat(p[x],i),i);
                }
                x=tp[p[x]];
            }
            for(int i=0;i<m;i++){
                mat rec=que(dfn[x],dfn[dn[x]],i);
                f[x][i]=rec.a;
                g[x][i]=rec.b;
            }
        }
        else{
            int res=0;
            for(int i=0;i<m;i++)
                if(pc[i&x]&1)sub(res,g[1][i]);
                else add(res,g[1][i]);
            cout<<1ll*res*inv[m]%mod<<'\n';
        }
    }
}

H - Niyaz and Small Degrees

这个是之前做过的题目,先咕了。

20251216 Ex

A - 动态图连通性

线段树分治的板子。

代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int op[500005],_x[500005],_y[500005],ans[500005];
map<pair<int,int>,int> cz;
vector<pair<int,int> > v[2000005];
void joi(int L,int R,pair<int,int> vl,int x=1,int l=1,int r=m){
    if(L>R)return;
    if(L<=l&&r<=R)return void(v[x].push_back(vl));
    int mid=(l+r)>>1;
    if(L<=mid)joi(L,R,vl,x<<1,l,mid);
    if(mid<R)joi(L,R,vl,x<<1|1,mid+1,r);
}
int fa[5005],sz[5005],tot;
pair<int,int> stk[500005];
int find(int x){return (x==fa[x]?x:find(fa[x]));}
int merge(int x,int y){
    if(find(x)==find(y))return 0;
    x=find(x),y=find(y);
    if(sz[x]>sz[y])swap(x,y);
    fa[x]=y;
    sz[y]+=sz[x];
    stk[++tot]=make_pair(x,y);
    return 1;
}
void pop(){
    auto top=stk[tot--];
    sz[top.second]-=sz[top.first];
    fa[top.first]=top.first;
}
void sol(int x=1,int l=1,int r=m){
    int ct=0;
    for(auto ed:v[x])ct+=merge(ed.first,ed.second);
    if(l==r){
        if(op[l]==2){
            ans[l]=(find(_x[l])==find(_y[l]));
        }
        while(ct--)pop();
        return;
    }
    int mid=(l+r)>>1;
    sol(x<<1,l,mid);sol(x<<1|1,mid+1,r);
    while(ct--)pop();
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;
    tot=0;
    for(int i=1;i<=m;i++){
        cin>>op[i]>>_x[i]>>_y[i];
        if(_x[i]>_y[i])swap(_x[i],_y[i]);
        if(op[i]==0)cz[make_pair(_x[i],_y[i])]=i;
        if(op[i]==1)
            joi(cz[make_pair(_x[i],_y[i])],i-1,make_pair(_x[i],_y[i])),
            cz.erase(make_pair(_x[i],_y[i]));
    }
    for(auto ed:cz)joi(ed.second,m,ed.first);
    sol();
    for(int i=1;i<=m;i++)if(op[i]==2)cout<<(ans[i]?"Y":"N")<<'\n';
}

B - Binary Table (CF662C)

直觉上首先是 \(n\) 很小可以状压(枚举),然后按照 \(m\) 那一维进行扫描。

假设已经知道行作用后的每一列 \(\{A\}\),答案就是 \(\sum \min(|A_i|,n-|A_i|)\)\(|\circ|\) 是 popcount。

这样不难做到 \(O(2^nm)\) 之类的复杂度。

试图把 \(m\) 消掉。把 \(m\) 列中行作用前为 \(c\) 的个数记为 \(a_c\)。不难做到 \(O(4^n)\)。(反而更劣了)

记上面那个 min 为 \(b_{a_i}\)

上面的和式写出来就是 \(f_{a}=\sum_{c} a_c b_{c \oplus a}=\sum_{c \oplus d=a}a_c b_d\)

直接 xor FWT 没了。

代码
#include<bits/stdc++.h>
const int mod=998244353;
#define popc __builtin_popcount
using namespace std;
int fpow(int a,int b=mod-2){
    int r=1;
    while(b){
        if(b&1)r=1ll*r*a%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return r;
}
int n,m;
char s[25][100005];
int a[1<<20],b[1<<20];
inline int add(int x,int y){return (x+y>=mod?x+y-mod:x+y);}
inline int sub(int x,int y){return (x<y?x+mod-y:x-y);}
#define fwt(a,n,op) do{\
    for(int FWT_len=2,FWT_mid=1;FWT_len<=(1<<n);FWT_len<<=1,FWT_mid<<=1)\
        for(int FWT_i=0;FWT_i<(1<<n);FWT_i+=FWT_len)\
            for(int FWT_j=0;FWT_j<FWT_mid;FWT_j++){\
                int FWT_y=a[FWT_i|FWT_j|FWT_mid];\
                a[FWT_i|FWT_j|FWT_mid]=sub(a[FWT_i|FWT_j],FWT_y),a[FWT_i|FWT_j]=add(a[FWT_i|FWT_j],FWT_y);\
            }\
    op(a,n);\
}while(0)
void dft(int *a,int n){}
void idft(int *a,int n){int t=fpow(fpow(2,n));for(int i=0;i<(1<<n);i++)a[i]=1ll*a[i]*t%mod;}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    for(int i=0;i<n;i++)cin>>(s[i]+1);
    for(int i=1;i<=m;i++){
        int bs=0;
        for(int j=0;j<n;j++)bs=bs<<1|(s[j][i]-'0');
        a[bs]++;
    }
    for(int i=0;i<(1<<n);i++)b[i]=min(popc(i),n-popc(i));
    fwt(a,n,dft);fwt(b,n,dft);
    for(int i=0;i<(1<<n);i++)a[i]=1ll*a[i]*b[i]%mod;
    fwt(a,n,idft);
    int ans=n*m;
    for(int i=0;i<(1<<n);i++)ans=min(ans,a[i]);
    cout<<ans;
}

C - 点分树 | 震波

单点修改,求 k 邻域和。

这个点分树套动态开点线段树是不是就好了。时空都是 \(O(n\log^2{n})\) 的吧。

是不是写边分树更好玩啊,二叉的话更好写吧。

正好之前只写过一次边分树(可持久化),再写一遍练练手。

代码
#include<bits/stdc++.h>
const int N=1e5,VN=N*70,VM=N*70;
using namespace std;
struct grp{
    int n,m=1;
    int hd[4*N+5],nx[4*N+5],to[4*N+5],w[4*N+5];
    void addedge(int u,int v,int c){
        to[++m]=v;w[m]=c;
        nx[m]=hd[u];
        hd[u]=m;
    }
}g1,g2;
int n,m,v[N+5];
namespace edt{
    //sgt
    namespace sgt{
        int v[VN+5],ch[VN+5][2],cnt;
        vector<int> bin;
        int newnode(){
            int x;
            if(!bin.empty())x=bin.back(),bin.pop_back();
            else x=++cnt;
            v[x]=ch[x][0]=ch[x][1]=0;
            return x;
        }
        void merge(int &x,int y,int l=0,int r=n){
            if(!x||!y)return void(x=x+y);
            v[x]+=v[y];
            merge(ch[x][0],ch[y][0]);
            merge(ch[x][1],ch[y][1]);
            bin.push_back(y);
            return;
        }
        void ins(int &x,int pos,int vl,int l=0,int r=n){
            if(!x)x=newnode();
            if(l==r)return void(v[x]+=vl);
            int mid=(l+r)>>1;
            if(pos<=mid)ins(ch[x][0],pos,vl,l,mid);
            else ins(ch[x][1],pos,vl,mid+1,r);
            v[x]=v[ch[x][0]]+v[ch[x][1]];
        }
        int que(int &x,int L,int R,int l=0,int r=n){
            if(!x||L>R)return 0;
            if(L<=l&&r<=R)return v[x];
            int mid=(l+r)>>1,res=0;
            if(L<=mid)res+=que(ch[x][0],L,R,l,mid);
            if(mid<R)res+=que(ch[x][1],L,R,mid+1,r);
            return res;
        }
    }
    //edt
    void adj(int x,int fa){
        int lst=x,fg=0;
        for(int i=g1.hd[x];i;i=g1.nx[i]){
            int j=g1.to[i];
            if(j==fa)continue;
            if(fg)g2.n++,g2.addedge(lst,g2.n,0),g2.addedge(g2.n,lst,0),lst=g2.n;
            fg=1;
            g2.addedge(lst,j,1);
            g2.addedge(j,lst,1);
            adj(j,x);
        }
    }
    #define Adj() (g2.n=g1.n,adj(1,0))
    bool vis[4*N+5];
    vector<int> fx[4*N+5];
    vector<int> ds[4*N+5];
    int sz[4*N+5],rte,rtv,nn;
    void getm(int x,int fa){
        sz[x]=1;
        for(int i=g2.hd[x];i;i=g2.nx[i]){
            int j=g2.to[i];
            if(j==fa||vis[i])continue;
            getm(j,x);sz[x]+=sz[j];
            if(max(sz[j],nn-sz[j])<rtv)rte=i,rtv=max(sz[j],nn-sz[j]);
        }
    }
    #define Getm(x,fa) (getm(x,fa),nn=sz[x],rte=-1,rtv=1e9,getm(x,fa))
    void dfs(int x,int fa,int typ,int dis){
        if(x<=n){
            fx[x].push_back(typ);
            ds[x].push_back(dis);
        }
        for(int i=g2.hd[x];i;i=g2.nx[i]){
            int j=g2.to[i];
            if(j==fa||vis[i])continue;
            dfs(j,x,typ,dis+g2.w[i]);
        }
    }
    void conquer(int x,int fa){
        Getm(x,fa);
        if(rte==-1)return;
        vis[rte]=vis[rte^1]=1;
        int u=g2.to[rte],v=g2.to[rte^1],w=g2.w[rte];
        dfs(u,v,0,0);dfs(v,u,1,w);
        conquer(u,v);conquer(v,u);
    }
    int tr[VM+5][2],ch[VM+5][2],cnt;
    vector<int> bin;
    int newnode(){
        int x;
        if(!bin.empty())x=bin.back(),bin.pop_back();
        else x=++cnt;
        tr[x][0]=tr[x][1]=ch[x][0]=ch[x][1]=0;
        return x;
    }
    int build(int x,int vl,int st=0){
        if(st==(int)fx[x].size())return 0;
        int nw=newnode();
        sgt::ins(tr[nw][fx[x][st]],ds[x][st],vl);
        ch[nw][fx[x][st]]=build(x,vl,st+1);
        return nw;
    }
    void merge(int &x,int y){
        if(!x||!y)return void(x=x+y);
        sgt::merge(tr[x][0],tr[y][0]);
        sgt::merge(tr[x][1],tr[y][1]);
        merge(ch[x][0],ch[y][0]);
        merge(ch[x][1],ch[y][1]);
        bin.push_back(y);
    }
}using namespace edt;
int rt;
int que(int rt,int x,int k,int st=0){
    if(st==(int)fx[x].size())return 0;
    int res=sgt::que(tr[rt][fx[x][st]^1],0,k-ds[x][st]);
    return res+que(ch[rt][fx[x][st]],x,k,st+1);
}
int lans;
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;g1.n=n;
    for(int i=1;i<=n;i++)cin>>v[i];
    for(int i=1;i<n;i++){
        int u,v;
        cin>>u>>v;
        g1.addedge(u,v,1);
        g1.addedge(v,u,1);
    }
    Adj();
    conquer(1,0);
    for(int i=1;i<=n;i++)merge(rt,build(i,v[i]));
    while(m--){
        int op,x,k;
        cin>>op>>x>>k;x^=lans;k^=lans;
        if(op==0)cout<<(lans=que(rt,x,k)+v[x])<<'\n';
        else{
            int tmp=build(x,k-v[x]);
            merge(rt,tmp);
            v[x]=k;
        }
    }
}

D - 捉迷藏

单点修改+查询黑点直径。

树上直径板子,直径是可合并信息。

代码
#include<bits/stdc++.h>
#define fin(x) freopen(#x".in","r",stdin)
#define fout(x) freopen(#x".out","w",stdout)
#define file(x) (fin(x),fout(x),0)
#define pii pair<int,int>
#define int long long
using namespace std;
const int N=1e5,W=17,inf=1e9;
int n,q,w,lans;
//维护树形态以及树上距离
int P,v[262144],tg[262144];
void add(int l,int r,int vl){
	l=l+P-1,r=r+P+1;int siz=1;
	while(l^r^1){
		if(l&1^1)v[l^1]+=siz*vl,tg[l^1]+=vl;
		if(r&1)v[r^1]+=siz*vl,tg[r^1]+=vl;
		l>>=1,r>>=1,siz<<=1;
		v[l]=v[l<<1]+v[l<<1|1]+tg[l]*siz;
		v[r]=v[r<<1]+v[r<<1|1]+tg[r]*siz;
	}
	for(l>>=1;l;l>>=1)v[l]=v[l<<1]+v[l<<1|1]+tg[l]*siz;
}
int que(int pos){
	pos=pos+P;int res=v[pos];
	for(pos>>=1;pos;pos>>=1)res+=tg[pos];
	return res;
}
vector<int> to[N+5];
int U[N+5],V[N+5];
int dep[N+5],dfn[N+5],edn[N+5],fid[N+5],cnt;
int sz[N+5],hs[N+5],f[N+5];
void dfs(int x=1,int fa=0){
	dfn[x]=++cnt;fid[cnt]=x;dep[x]=dep[fa]+1;
	sz[x]=1;f[x]=fa;
	for(auto ed:to[x]){
		if(ed==fa)continue;
		dfs(ed,x);
		sz[x]+=sz[ed];
		if(!hs[x]||sz[hs[x]]<=sz[ed])hs[x]=ed;
	}
	edn[x]=cnt;
}
int tp[N+5];
void dfS(int x=1,int fa=0,int top=1){
	tp[x]=top;
	if(hs[x])dfS(hs[x],x,top);
	for(auto ed:to[x]){
		if(ed==fa||ed==hs[x])continue;
		dfS(ed,x,ed);
	}
}
int lca(int x,int y){
	while(tp[x]!=tp[y]){
		if(dep[tp[x]]>dep[tp[y]])x=f[tp[x]];
		else y=f[tp[y]];
	}
	return (dep[x]<dep[y]?x:y);
}
int dis(int x,int y){
	if(!x||!y)return -inf;
	int l=lca(x,y);
	return que(dfn[x])+que(dfn[y])-2*que(dfn[l]);
}
//标记
struct mes{int a,b,ds;};
mes operator+(mes x,mes y){
	pair<int,pii> res={x.ds,{x.a,x.b}},tpm;
	tpm={dis(x.a,y.a),{x.a,y.a}};res=max(res,tpm);
	tpm={dis(x.a,y.b),{x.a,y.b}};res=max(res,tpm);
	tpm={dis(x.b,y.a),{x.b,y.a}};res=max(res,tpm);
	tpm={dis(x.b,y.b),{x.b,y.b}};res=max(res,tpm);
	tpm={y.ds,{y.a,y.b}};res=max(res,tpm);
	return {res.second.first,res.second.second,res.first};
}
mes vv[(N<<2)+5];
int sgn[N+5];
void build(int x=1,int l=1,int r=n){
	if(l==r)return void(vv[x]={fid[l],fid[l],0});
	int mid=(l+r)>>1;
	build(x<<1,l,mid);build(x<<1|1,mid+1,r);
	vv[x]=vv[x<<1]+vv[x<<1|1];
}
void upd(int L,int R,int x=1,int l=1,int r=n){
	if(L>R)return;
	if(L<=l&&r<=R){
		if(l==r){
			if(sgn[l])vv[x]={fid[l],fid[l],0};
			else vv[x]={0,0,-inf};
		}
		else vv[x]=vv[x<<1]+vv[x<<1|1];
		return;
	}
	int mid=(l+r)>>1;
	if(L<=mid)upd(L,R,x<<1,l,mid);
	if(mid<R)upd(L,R,x<<1|1,mid+1,r);
	vv[x]=vv[x<<1]+vv[x<<1|1];
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	P=1;while(P<=n+1)P<<=1;
	for(int i=1;i<=n;i++)sgn[i]=1;
	for(int i=1;i<n;i++){
		cin>>U[i]>>V[i];
		to[U[i]].push_back(V[i]);
		to[V[i]].push_back(U[i]);
	}
	dfs();dfS();
	for(int i=1;i<n;i++){
		if(dep[U[i]]>dep[V[i]])add(dfn[U[i]],edn[U[i]],1);
		else add(dfn[V[i]],edn[V[i]],1);
	}
	build();
	cin>>q;
	while(q--){
		char op;
		int x;
		cin>>op;
		if(op=='G'){
			int res=vv[1].ds;
			if(res==-inf)cout<<"-1\n";
			else cout<<res<<'\n';
		}
		else{
			cin>>x;
			sgn[dfn[x]]^=1;
			upd(dfn[x],dfn[x]);
		}
	}
}

E - Souvenirs

其实是先做了 F 再来做这题的。所以看起来就觉得像是支配对。

\((a,b)\) 支配 \((x,y)\) 肯定就是 \(x \leq a<b \leq y,|v_a-v_b|<|v_x-v_y|\)

有一个直觉是 \(v_a,v_b\) 不能被 \(v_x,v_y\) 夹在中间,必要性显然。

其实在弱化一点把 \(a=x\) 或者 \(b=y\) 那就是中间不能有数。那么前面比我大的可行的一定是个递增序列,比我小的一定递减。

我们先考虑前面比我大的。那么 \(i < j < k,a_k < a_i < a_j,a_i - a_k < a_j - a_k,a_j - a_i\)

所以就是 \(a_k<a_i<\min(a_j,\frac{a_j+a_k}{2})\)

考虑这个差分 \(a_i-a_k<\frac{a_j-a_k}{2}\),这个差分缩小的势能是 \(\log{V}\) 的,所以没啥问题。

小于部分不是一样的吗?做完了,两只 log。

关于咋写?卧槽我好像真不会写啥正常做法。

感觉上你偏序都有两只 log 了这里多带只 log 也没啥,就直接分治归并维护序列,然后二分(平衡树)往前找就好吧。但这样写起来比 f 的点分还达芬吧。

写一半突然想起每次插入的是最小值,所以就是 pop_front 吧。但是不管了,平衡树可以 spilt 爽。

比较好的是看到别人的题解发现可以做值域翻转,这样就不用再写一次小于了。

还有记得 \(a_i=a_j\) 会杀死比赛。但我懒得写进前面那个复杂的过程里。所以最后记得把 \(O(n)\) 个相等加进去。

代码
#include<bits/stdc++.h>
#define pii pair<int,int>
#define poi pair<pii,int>
#define int long long
const int inf=1e16;
using namespace std;
int n,a[100005];
vector<poi> res;
int m;
struct Que{int l,r,id;}Q[300005];
int ans[300005];
int v[400005];
void ins(int pos,int vl,int x=1,int l=1,int r=n){
    if(l==r)return void(v[x]=min(v[x],vl));
    int mid=(l+r)>>1;
    if(pos<=mid)ins(pos,vl,x<<1,l,mid);
    else ins(pos,vl,x<<1|1,mid+1,r);
    v[x]=min(v[x<<1],v[x<<1|1]);
}
int que(int L,int R,int x=1,int l=1,int r=n){
    if(L>R)return inf;
    if(L<=l&&r<=R)return v[x];
    int mid=(l+r)>>1,res=inf;
    if(L<=mid)res=min(res,que(L,R,x<<1,l,mid));
    if(mid<R)res=min(res,que(L,R,x<<1|1,mid+1,r));
    return res;
}
int id[100005],tid[100005];
vector<int> gd[100005];
//
mt19937 rnd(time(0));
struct node{int v,pos,hv,ls,rs,sz;}tn[100005];
#define ls(x) tn[x].ls
#define rs(x) tn[x].rs
int cnt;
int newnode(int vl,int pos){
    tn[++cnt]={vl,pos,(int)rnd(),0,0,1};
    return cnt;
}
void pushup(int x){tn[x].sz=tn[ls(x)].sz+tn[rs(x)].sz+1;}
void spilt(int rt,int vl,int &x,int &y){
    if(!rt)return void(x=y=0);
    if(tn[rt].v<=vl)x=rt,spilt(rs(rt),vl,rs(x),y);
    else y=rt,spilt(ls(rt),vl,x,ls(y));
    pushup(rt);
}
void spiltP(int rt,int pos,int &x,int &y){
    if(!rt)return void(x=y=0);
    if(tn[rt].pos<=pos)x=rt,spiltP(rs(rt),pos,rs(x),y);
    else y=rt,spiltP(ls(rt),pos,x,ls(y));
    pushup(rt);
}
int merge(int x,int y){
    if(!x||!y)return x+y;
    if(tn[x].hv<tn[y].hv)return (rs(x)=merge(rs(x),y),pushup(x),x);
    return (ls(y)=merge(x,ls(y)),pushup(y),y);
}
int maxn(int x){
    if(!rs(x))return tn[x].pos;
    return maxn(rs(x));
}

void sol(int l=1,int r=n){
    if(l==r)return void(id[l]=l);
    int mid=(l+r)>>1;
    sol(l,mid);sol(mid+1,r);
    int pl=l-1,pr=mid,pn=l-1,stk=cnt=0;
    while(pl<mid&&pr<r){
        if(a[id[pl+1]]>a[id[pr+1]]){
            pl++;tid[++pn]=id[pl];
            int xx,yy;
            spiltP(stk,id[pl],xx,yy);
            stk=merge(newnode(a[id[pl]],id[pl]),yy);
        }
        else{
            pr++;tid[++pn]=id[pr];
            int t,vln=a[id[pr]],xx,yy;
            if(gd[id[pr]].empty())t=inf;
            else t=a[gd[id[pr]].back()];
            while(1){
                spilt(stk,(t+vln-1)/2,xx,yy);
                if(!xx){
                    stk=yy;
                    break;
                }
                int tmp=maxn(xx);
                res.push_back({{tmp,id[pr]},abs(a[id[pr]]-a[tmp])});
                gd[id[pr]].push_back(tmp);
                t=a[tmp];
                stk=merge(xx,yy);
            }
        }
    }
    while(pl<mid){
        pl++;tid[++pn]=id[pl];
        int xx,yy;
        spilt(stk,a[id[pl]],xx,yy);
        stk=merge(newnode(a[id[pl]],id[pl]),yy);
    }
    while(pr<r){
        pr++;tid[++pn]=id[pr];
        int t,vln=a[id[pr]],xx,yy;
        if(gd[id[pr]].empty())t=inf;
        else t=a[gd[id[pr]].back()];
        while(1){
            spilt(stk,(t+vln-1)/2,xx,yy);
            if(!xx){
                stk=yy;
                break;
            }
            int tmp=maxn(xx);
            res.push_back({{tmp,id[pr]},abs(a[id[pr]]-a[tmp])});
            gd[id[pr]].push_back(tmp);
            t=a[tmp];
            stk=merge(xx,yy);
        }
    }
    memcpy(id+l,tid+l,(r-l+1)*sizeof(int));
}
map<int,int> lst;
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    fill(v+1,v+400001,inf);
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    for(int i=1;i<=n;i++){
        if(lst.count(a[i]))res.push_back({{lst[a[i]],i},0});
        lst[a[i]]=i;
    }
    sol();
    for(int i=1;i<=n;i++)gd[i].clear(),a[i]=-a[i];
    sol();
    int m;
    cin>>m;
    for(int i=1;i<=m;i++)cin>>Q[i].l>>Q[i].r,Q[i].id=i;
    sort(res.begin(),res.end());
    sort(Q+1,Q+1+m,[](Que x,Que y){return x.l<y.l;});
    for(int r=(int)res.size()-1,l=r+1,i=m+1;r>=0;r=l-1,l=r+1){
        while(l>0&&res[l-1].first.first==res[r].first.first){
            l--;
            ins(res[l].first.second,res[l].second);
        }
        while(i-1&&(l==0||Q[i-1].l>res[l-1].first.first)){
            i--;
            ans[Q[i].id]=que(Q[i].l,Q[i].r);
        }
    }
    for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
}

F - Tree Distance

支配对+二维偏序。

考虑到 \((x,y),(a,b),x \leq a<b \leq y,dis(x,y) \geq dis(a,b)\) 那么 \((x,y)\) 就没啥用了。

但是这个还是很难受只能 \(O(n^2)\) 搞。

我们考虑树分治。可以直接把 \(dis(x,y)\) 变成 \(d_x+d_y\)。一般来说还要满足 \(x,y\) 来自不同子树,但是这里求的是 min,可以把这个限制弱化掉。

就变成了 \((x,y),(a,b),x \leq a < b \leq y,d_x+d_y \geq d_a+d_b\)。然后考虑极端情况 \(x=a/y=b\),可以发现如果 \((x,y)\) 有用那么他们是区间最小和次小值。

考虑笛卡尔树,相当于 \(x\) 只能和第一个左父亲/右父亲匹配,只有 \(O(n)\) 个点对。所以这样求出的支配对就是 \(O(n\log{n})\) 个的。具体写起来,就是排序后跑笛卡尔树栈构建算法,把右链遍历到的都选出来就好了。

把支配对跑出来后直接二维偏序就好。

代码
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define poi pair<pii,int>
const int N=2e5,inf=1e16;
using namespace std;
int n;
vector<pii> to[N+5];
vector<poi> res;
bool vis[N+5];
int sz[N+5],mson[N+5],rtp,rtv,nn;
void getm(int x,int fa){
    sz[x]=1;mson[x]=0;
    for(auto ed:to[x]){
        if(ed.first==fa||vis[ed.first])continue;
        getm(ed.first,x);sz[x]+=sz[ed.first];mson[x]=max(mson[x],sz[ed.first]);
    }
    mson[x]=max(mson[x],nn-sz[x]);
    if(mson[x]<rtv)rtp=x,rtv=mson[x];
}
#define Getm(x,fa) (getm(x,fa),nn=sz[x],rtp=-1,rtv=1e9,getm(x,fa))
vector<pii> tmp;
void dfs(int x,int fa,int dis){
    tmp.push_back({x,dis});
    for(auto ed:to[x]){
        if(ed.first==fa||vis[ed.first])continue;
        dfs(ed.first,x,dis+ed.second);
    }
}
//??????.
int stk[N+5],tot;
void conquer(int x,int fa){
    Getm(x,fa);
    int nw=rtp;vis[nw]=1;
    tmp.clear();
    tmp.push_back({nw,0});
    for(auto ed:to[nw]){
        if(vis[ed.first])continue;
        dfs(ed.first,nw,ed.second);
    }
    sort(tmp.begin(),tmp.end());
    tot=0;
    for(int i=0,si=(int)tmp.size();i<si;i++){
        while(tot&&tmp[stk[tot]].second>=tmp[i].second){
            res.push_back({{tmp[stk[tot]].first,tmp[i].first},tmp[stk[tot]].second+tmp[i].second});
            tot--;
        }
        if(tot)res.push_back({{tmp[stk[tot]].first,tmp[i].first},tmp[stk[tot]].second+tmp[i].second});
        stk[++tot]=i;
    }
    for(auto ed:to[nw]){
        if(vis[ed.first])continue;
        conquer(ed.first,nw);
    }
}
struct Que{int l,r,id;}Q[N*5+5];
int ans[N*5+5];
int v[(N<<2)+5];
void ins(int pos,int vl,int x=1,int l=1,int r=n){
    if(l==r)return void(v[x]=min(v[x],vl));
    int mid=(l+r)>>1;
    if(pos<=mid)ins(pos,vl,x<<1,l,mid);
    else ins(pos,vl,x<<1|1,mid+1,r);
    v[x]=min(v[x<<1],v[x<<1|1]);
}
int que(int L,int R,int x=1,int l=1,int r=n){
    if(L>R)return 1e16;
    if(L<=l&&r<=R)return v[x];
    int mid=(l+r)>>1,res=1e16;
    if(L<=mid)res=min(res,que(L,R,x<<1,l,mid));
    if(mid<R)res=min(res,que(L,R,x<<1|1,mid+1,r));
    return res;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    fill(v+1,v+1+(N<<2),1e16);
    cin>>n;
    for(int i=1;i<n;i++){
        int u,v,w;
        cin>>u>>v>>w;
        to[u].push_back({v,w});
        to[v].push_back({u,w});
    }
    conquer(1,0);
    sort(res.begin(),res.end());
    int q;
    cin>>q;
    for(int i=1;i<=q;i++)cin>>Q[i].l>>Q[i].r,Q[i].id=i;
    sort(Q+1,Q+1+q,[](Que x,Que y){return x.l<y.l;});
    for(int r=(int)res.size()-1,l=r+1,i=q+1;r>=0;r=l-1,l=r+1){
        while(l>0&&res[l-1].first.first==res[r].first.first){
            l--;
            ins(res[l].first.second,res[l].second);
        }
        while(i>1&&(l==0||Q[i-1].l>res[l-1].first.first)){
            i--;
            if(Q[i].l>=Q[i].r)ans[Q[i].id]=-1;
            else ans[Q[i].id]=que(Q[i].l,Q[i].r);
        }
    }
    for(int i=1;i<=q;i++)
        cout<<ans[i]<<'\n';
}
posted @ 2025-12-22 20:53  Aysct  阅读(1)  评论(0)    收藏  举报