题解:P6666 [清华集训 2016] 数据交互

前言

本文同步自洛谷专栏,题目传送门

过年了就应该写这种题号吉利的题目,预祝新年快乐!

这是一篇 \(O(n\log^2{n})\) 的树剖题解,前置知识是基础的 ddp。

解题

约定

树根为 \(1\) 号节点。

对于从 \(u\)\(v\) 的路径,记作 \(path(u,v)\)\((u,v)\)

称操作中给出的数据交互请求路径为交互路径,题面中提及的极其重要的路径为答案路径

点是路径的 LCA,即点是路径两端点的 LCA。

\(w(i,j)\) 表示以 \(i,j\) 为端点的增加操作的权值。

\(son_u\) 表示 \(u\) 的子节点的集合。

\(hson_u\) 表示 \(u\) 的重儿子。

\(u\) 的子树最优链表示以 \(u\) 为一个端点,\(u\) 子树内一点 \(v\) 为另一端点的路径的最大权值。

初步观察

删除操作显然可以规约为增加操作,额外记录每次操作的内容即可,下文讨论的操作默认为增加操作

与答案路径相交的交互路径的权值都要被删去,由于不能重复计算,我们可以考虑经典的点边容斥(点上加正权,边上加负权)。

如果只有一次询问,我们容易有树上 dp 做法:对每个点维护向子树最优链的最大值、次大值,更新答案即可。

然而现在这个问题是动态的,结合路径思考,容易想到重链剖分。

刻画问题

考虑如何维护有关信息,我们发现边和点都有权值较为麻烦,考虑简化一些。

对于点 \(u\) 和它通向父亲的边 \((u,fa_u)\),如果同时被包含在答案路径和交互路径中,是没有贡献的。

  • 如果不同时在答案路径中,则 \(u\) 为答案路径的 LCA。

  • 如果不同时在交互路径中,则 \(u\) 为交互路径的 LCA。

对于第二种情况,我们令 \(ex_u=\sum_{ \operatorname{LCA}(i,j)=u}w(i,j)\)

同时我们再令 \(W_u=\sum_{u\in path(i,j)}w(i,j)\),结合上述第一种情况,可知一条答案路径的代价:\(ans=W_{ \operatorname{LCA}(u,v)}+\sum_{x\in path(u,v)\land x\ne \operatorname{LCA}(u,v)}ex_x\)

注:如果对 \(W\) 的定义除去了 LCA,也应当可以解决问题,这里不赘述。

分析

\(f_u\) 表示从 \(u\) 向子树 \(ex\) 最优链的权值和(含 \(u\) 处的)。

则有 \(f_u=ex_u+\max_{v\in son_u}(f_v)\),统计答案则需额外记录次大值。

改写成 ddp 形式,设 \(fi_u,se_u\) 分别表示 \(u\) 的所有轻儿子的 \(f\) 的最大值、次大值。

\(f_u=ex_u+\max(fi_u,f_{hson_u})\)

统计答案时用 \(W_u+\max(fi_u+f_{hson_u},fi_u+se_u)\) 更新。

对于 \(fi,se\),可以通过重标号用线段树维护,然而使用 multiset 维护更加方便,本文采用后者。

统计答案显然不能对每个点考虑,考虑到每次修改操作仅影响 \(O(\log{n})\) 条重链,可以按重链统计,令 \(g_u\) 表示从 \(u\) 所在重链底端到 \(u\) 的答案的最大值。

这里应用的矩阵显然是 \((\max,+)\) 矩阵,行列向量的选择上,建议使用列向量,因为对重链的答案统计是从深到浅,由 dfn 大到 dfn 小。

转移写成矩阵形式,设 \(inf\) 表示无穷。

\[\begin{bmatrix}f_u\\g_u\\0\end{bmatrix}=\begin{bmatrix}ex_u&inf&fi_u+ex_u\\fi_u+W_u&0&fi_u+se_u+W_u\\inf&inf&0\end{bmatrix}\begin{bmatrix}f_v\\g_v\\0\end{bmatrix} \]

\(ex\) 的修改是单点修改,和一般的 ddp 处理方式相同。

然而 \(W\) 的修改是区间修改,考虑能否打懒标记,一般化矩阵乘法如下:

\[\begin{bmatrix}a&inf&b\\c&0&d\\inf&inf&0\end{bmatrix}\begin{bmatrix}e&inf&f\\g&0&h\\inf&inf&0\end{bmatrix}=\begin{bmatrix}a+e&inf&\max(a+f,b)\\\max(c+e,g)&0&\max(c+f,h,d)\\inf&inf&0\end{bmatrix} \]

\(W\) 有关的变量 \(c,d,g,h\),同时增加 \(W_u\) 后,所得结果的对应位置的 \(\max\) 中均增加 \(W_u\),则只需对矩阵的 \(c,d\) 位打标记即可。

最终的答案由各个重链的答案汇总得到,用 multiset 维护较为方便。

实现

时间复杂度 \(O(n\log^2{n})\),空间线性。

矩阵有效位置较少,只用存四个数,常数会较优。

不想判 multiset 空的话,可以提前塞入充分多的 \(0\)

注意,对 \(W\) 的修改也需要在线段树上更新,不要只在修改 \(ex\) 时更新。

$\red{\text{code}}$
#include<bits/stdc++.h>
using namespace std;
#define gc() (rp1==rp2&&(rp2=(rp1=buf)+fread(buf,1,IO,stdin))==buf?EOF:*rp1++)
#define pc(a) (wrp==obuf+IO&&(fwrite(obuf,1,IO,stdout),wrp=obuf),(*wrp++)=a)
#define ll long long
#define N 100005
#define mid ((l+r)>>1)
#define pll pair<long long,long long>

struct edge{
    int v,nxt;
}E[N<<1];

inline ll pmax(ll a,ll b){return a<b?b:a;}
inline void cmax(ll &a,ll b){(a<b)&&(a=b);}
/*
   a -inf b   f(当前点向重儿子方向延伸的最大值)
   c    0 d * g(重链总的最大值)
-inf -inf 0   0
*/
struct matrix{
    ll a,b,c,d;//只有四个位置有效
    inline friend matrix operator *(const matrix &x,const matrix &y){
        matrix z;z.a=x.a+y.a;
        z.b=pmax(x.a+y.b,x.b),z.c=pmax(x.c+y.a,y.c);
        z.d=pmax(x.c+y.b,pmax(x.d,y.d));return z;
    }
}tr[N<<2];

struct ope{
    int u,v,w;
}Q[N];

const int IO=1<<22;
char buf[IO+1],obuf[IO+1],*wrp=obuf,*rp1,*rp2;
int n,m,tot,head[N],dep[N],tp[N];
int siz[N],son[N],dfn[N],rdfn[N],fa[N];
ll ex[N],tag[N<<2];//tag 仅对 c,d 项有影响
multiset<ll>mp[N];//存储每个点轻儿子的贡献
//0 位存储全局的重链的答案
//每个点的 fi,se 是不包括自身的 ex 的
inline int read(){
    int a=0,c=gc();
    while(!isdigit(c)) c=gc();
    while(isdigit(c)) a=10*a+c-'0',c=gc();
    return a;
}

inline void write(ll x){
    int sk[20],top=0;
    do{
        sk[++top]=x%10,x/=10;
    }while(x);
    while(top) pc(sk[top--]+'0');
    pc('\n');
}

inline int read_op(){
    int c=gc();
    while(c!='+'&&c!='-') c=gc();
    return c=='+'; 
}

void dfs1(int u){
    siz[u]=1,mp[u].insert(0ll),mp[u].insert(0ll);//避免后续访问失败
    for(int i=head[u],v;i;i=E[i].nxt){
        if((v=E[i].v)==fa[u]) continue;
        dep[v]=dep[u]+1,fa[v]=u,dfs1(v),siz[u]+=siz[v];
        if(siz[v]>siz[son[u]]) son[u]=v;
    }
}

void dfs2(int u,int top){
    dfn[u]=++tot,tp[u]=top,rdfn[tot]=u,siz[top]=tot;
    if(son[u]) dfs2(son[u],top);
    for(int i=head[u];i;i=E[i].nxt){
        if(E[i].v==fa[u]||E[i].v==son[u]) continue;
        dfs2(E[i].v,E[i].v),mp[u].insert(0ll);//最好塞足 0
        mp[0].insert(0ll);
    }
}

void build(int p,int l,int r){//清空线段树
    tag[p]=0,tr[p]={0ll,0ll,0ll,0ll};
    if(l==r) return;
    build(p<<1,l,mid),build(p<<1|1,mid+1,r);
}

inline void Tag(int p,ll d){
    tr[p].c+=d,tr[p].d+=d,tag[p]+=d;
}

inline void pushdown(int p,ll &d){
    Tag(p<<1,d),Tag(p<<1|1,d),d=0;
}

void modify(int ql,int qr,int d,int p=1,int l=1,int r=n){
    if(ql<=l&&r<=qr) return Tag(p,d);
    if(tag[p]) pushdown(p,tag[p]);
    if(ql<=mid) modify(ql,qr,d,p<<1,l,mid);
    if(mid<qr) modify(ql,qr,d,p<<1|1,mid+1,r);
    tr[p]=tr[p<<1]*tr[p<<1|1];
}

pll query(int ql,int qr,pll s,int p=1,int l=1,int r=n){
    if(ql<=l&&r<=qr){//列向量左乘矩阵
        cmax(s.second,pmax(s.first+tr[p].c,tr[p].d));
        s.first=pmax(s.first+tr[p].a,tr[p].b);
        return s;
    }
    if(tag[p]) pushdown(p,tag[p]);
    if(mid<qr) s=query(ql,qr,s,p<<1|1,mid+1,r);
    if(ql<=mid) s=query(ql,qr,s,p<<1,l,mid);
    return s;//注意先右再左
}

void mdf(int q,int p=1,int l=1,int r=n){//单点更新 ex,fi,se
    if(l==r){
        int a=rdfn[q];ll e=ex[a],f,s,w;
        w=tr[p].c-tr[p].b+tr[p].a;//先倒推出 w
        f=*prev(mp[a].end()),s=*prev(prev(mp[a].end()));
        tr[p]={e,e+f,f+w,f+s+w};return;
    }
    if(tag[p]) pushdown(p,tag[p]);
    if(q<=mid) mdf(q,p<<1,l,mid);
    else mdf(q,p<<1|1,mid+1,r);
    tr[p]=tr[p<<1]*tr[p<<1|1];
}

inline void check(int u,int l,int r,int w){
    pll lst=query(dfn[tp[u]],siz[tp[u]],{0ll,0ll}),now;
    if(w) modify(l,r,w);
    mdf(dfn[u]),now=query(dfn[tp[u]],siz[tp[u]],{0ll,0ll});
    mp[0].erase(mp[0].find(lst.second));//注意全局答案更新
    mp[0].insert(now.second),u=fa[tp[u]];
    if(!u) return;//不要修改 0 位上的 multiset
    mp[u].erase(mp[u].find(lst.first));//注意使用迭代器访问
    mp[u].insert(now.first);
}

inline void chain_modify(int u,int v,int w){
    for(pll lst,now;tp[u]!=tp[v];u=fa[tp[u]]){//修改 W 值
        if(dep[tp[u]]<dep[tp[v]]) u^=v^=u^=v;
        check(u,dfn[tp[u]],dfn[u],w);
    }    
    if(dep[u]<dep[v]) u^=v^=u^=v;//v 成为 lca
    ex[v]+=w;
    for(;v;v=fa[tp[v]]) check(v,dfn[v],dfn[u],w),w=0;
}   

inline void del(int t,int i){
    Q[i]=Q[t],chain_modify(Q[t].u,Q[t].v,Q[i].w=-Q[t].w);
}

int main(){
    n=read(),m=read();
    for(int i=1;i<n;i++){
        int u=read(),v=read();
        E[i<<1]={v,head[u]},head[u]=i<<1;
        E[i<<1|1]={u,head[v]},head[v]=i<<1|1;
    }
    dfs1(1),dfs2(1,1),build(1,1,n),mp[0].insert(0);
    for(int i=1,o,u,v,w;i<=m;i++){
        o=read_op(),u=read();
        if(o==1){
            v=read(),w=read(),Q[i]={u,v,w};
            chain_modify(u,v,w);
        } 
        else del(u,i);
        write(*mp[0].rbegin());
    }
    return fwrite(obuf,1,wrp-obuf,stdout),0;
}


posted @ 2026-06-05 11:12  Wxb2010  阅读(4)  评论(0)    收藏  举报