洛谷P4338/Vijos2043/LOJ2434/UOJ374/BZOJ5212[ZJOI2018]历史(DP+LCT)

给定一棵树,已知每个点的$access$次数,求虚/实边切换次数的最大值。
首先不考虑修改,设一个点$u$修改次数为$a_u$,下属子树的总修改次数为$S_u$,$h_u=\max(a_u,\max{S_v}\text{(v为u的子节点)})$,则可知$2h_u>S_u$时,$u$结点的最大冲突次数为$2(S_u-h_u)$;否则为$S_u-1$。(可以自己试几次,保证理解)
对于$2h_u>S_u$的结点,我们注意到修改它$S_v==h_u$的孩子时答案是不变的,只有修改其它孩子时答案才发生变化,所以我们从u向它连一条实边(不难看出这样的实边最多只有一条),其它孩子连虚边,则可以用$LCT$维护,只有沿虚边向上跳时才需要考虑更新答案和虚实边切换(可能不切换,这点要注意)。
可以发现这种连法具有类似树剖的良好性质,复杂度$O(mlongn)$

#include<cstdio>
typedef long long ll;
const int N=400050;
char rB[1<<21],*S,*T,wB[1<<21];
int wp=-1;
inline char gc(){return S==T&&(T=(S=rB)+fread(rB,1,1<<21,stdin),S==T)?EOF:*S++;}
inline void flush(){fwrite(wB,1,wp+1,stdout);wp=-1;}
inline void pc(char c){if(wp+1==(1<<21))flush();wB[++wp]=c;}
inline int rd(){
    char c=gc();
    while(c<48||c>57)c=gc();
    int x=c&15;
    for(c=gc();c>=48&&c<=57;c=gc())x=(x<<3)+(x<<1)+(c&15);
    return x;
}
short buf[25];
inline void wt(ll x){
    short l=-1;
    while(x>9){
        buf[++l]=x%10;
        x/=10;
    }
    pc(x|48);
    while(l>=0)pc(buf[l--]|48);
    pc('\n');
}
int G[N],to[N<<1],nxt[N<<1],sz=0,f[N],ch[N][2];
ll a[N],sum[N],ima[N],ans=0;
inline void pushup(int o){sum[o]=a[o]+sum[ch[o][0]]+sum[ch[o][1]]+ima[o];}
inline bool dir(int o){return ch[f[o]][1]==o;}
inline bool isrt(int o){return ch[f[o]][0]!=o&&ch[f[o]][1]!=o;}
inline ll calc(int u,ll s,ll h){return ch[u][1]?(s-h<<1):((a[u]<<1)>s?(s-a[u]<<1):s-1);}
inline void rot(int o){
    int fa=f[o];
    bool d=dir(o);
    f[o]=f[fa];
    if(!isrt(fa))ch[f[fa]][dir(fa)]=o;
    if(ch[fa][d]=ch[o][d^1])f[ch[fa][d]]=fa;
    f[ch[o][d^1]=fa]=o;
    pushup(fa);pushup(o);
}
inline void splay(int o){
    for(;!isrt(o);rot(o))if(!isrt(f[o]))rot((dir(f[o])^dir(o))?o:f[o]);
}
inline void add(int u,int v){
    to[++sz]=v;nxt[sz]=G[u];G[u]=sz;
    to[++sz]=u;nxt[sz]=G[v];G[v]=sz;
}
void dfs(int u,int fa){
    int i,v,p=0;
    ll maxn=a[u];
    f[u]=fa;
    sum[u]=a[u];
    for(i=G[u];i;i=nxt[i])if((v=to[i])!=fa){
        dfs(v,u);
        sum[u]+=sum[v];
        if(sum[v]>maxn)maxn=sum[p=v];
    }
    ans+=(maxn<<1)>sum[u]?(sum[u]-maxn<<1):sum[u]-1;
    if(p&&(maxn<<1)>sum[u])ch[u][1]=p;
    ima[u]=sum[u]-sum[ch[u][1]]-a[u];
}
inline void update(int x,int w){
    int y=x;
    splay(x);
    ll s=sum[x]-sum[ch[x][0]],h=sum[ch[x][1]];
    ans-=calc(x,s,h);
    a[x]+=w;sum[x]+=w;s+=w;
    if((h<<1)<=s){ima[x]+=h;ch[x][1]=0;}
    ans+=calc(x,s,h);
    pushup(x);
    for(x=f[x];x;x=f[y=x]){
        splay(x);
        s=sum[x]-sum[ch[x][0]];h=sum[ch[x][1]];
        ans-=calc(x,s,h);
        sum[x]+=w;ima[x]+=w;s+=w;
        if((h<<1)<=s){
            ima[x]+=h;ch[x][1]=h=0;
            if((sum[y]<<1)>s){
                ima[x]-=(h=sum[y]);
                ch[x][1]=y;
            }
        }
        ans+=calc(x,s,h);
        pushup(x);
    }
}
int main(){
    int n=rd(),m=rd(),i,u,v;
    for(i=1;i<=n;++i)a[i]=rd();
    for(i=1;i<n;++i){
        u=rd();v=rd();
        add(u,v);
    }
    dfs(1,0);
    wt(ans);
    while(m--){
        u=rd();v=rd();
        update(u,v);
        wt(ans);
    }
    flush();
    return 0;
}
View Code

不过出于一些奇怪的原因Vijos上初始化的dfs会爆栈,考虑用bfs求出拓扑序,然后逆着拓扑序处理即可。

#include<cstdio>
#include<queue>
using namespace std;
typedef long long ll;
const int N=400050;
char rB[1<<21],*S,*T,wB[1<<21];
int wp=-1;
inline char gc(){return S==T&&(T=(S=rB)+fread(rB,1,1<<21,stdin),S==T)?EOF:*S++;}
inline void flush(){fwrite(wB,1,wp+1,stdout);wp=-1;}
inline void pc(char c){if(wp+1==(1<<21))flush();wB[++wp]=c;}
inline int rd(){
    char c=gc();
    while(c<48||c>57)c=gc();
    int x=c&15;
    for(c=gc();c>=48&&c<=57;c=gc())x=(x<<3)+(x<<1)+(c&15);
    return x;
}
short buf[25];
inline void wt(ll x){
    short l=-1;
    while(x>9){
        buf[++l]=x%10;
        x/=10;
    }
    pc(x|48);
    while(l>=0)pc(buf[l--]|48);
    pc('\n');
}
int G[N],to[N<<1],nxt[N<<1],sz=0,f[N],ch[N][2],r[N];
ll a[N],sum[N],ima[N],ans=0;
queue<int> Q;
inline void pushup(int o){sum[o]=a[o]+sum[ch[o][0]]+sum[ch[o][1]]+ima[o];}
inline bool dir(int o){return ch[f[o]][1]==o;}
inline bool isrt(int o){return ch[f[o]][0]!=o&&ch[f[o]][1]!=o;}
inline ll calc(int u,ll s,ll h){return ch[u][1]?(s-h<<1):((a[u]<<1)>s?(s-a[u]<<1):s-1);}  //计算冲突值
inline void rot(int o){
    int fa=f[o];
    bool d=dir(o);
    f[o]=f[fa];
    if(!isrt(fa))ch[f[fa]][dir(fa)]=o;
    if(ch[fa][d]=ch[o][d^1])f[ch[fa][d]]=fa;
    f[ch[o][d^1]=fa]=o;
    pushup(fa);pushup(o);
}
inline void splay(int o){
    for(;!isrt(o);rot(o))if(!isrt(f[o]))rot((dir(f[o])^dir(o))?o:f[o]);
}
//LCT的Splay部分
inline void add(int u,int v){
    to[++sz]=v;nxt[sz]=G[u];G[u]=sz;
    to[++sz]=u;nxt[sz]=G[v];G[v]=sz;
}
inline void work(int u){  //处理u
    int i,v,p=0;
    ll maxn=a[u];
    sum[u]=a[u];
    for(i=G[u];i;i=nxt[i])if((v=to[i])!=f[u]){
        sum[u]+=sum[v];
        if(sum[v]>maxn)maxn=sum[p=v];
    }
    ans+=(maxn<<1)>sum[u]?(sum[u]-maxn<<1):sum[u]-1;
    if(p&&(maxn<<1)>sum[u])ch[u][1]=p;  //计算答案和加边
    ima[u]=sum[u]-sum[ch[u][1]]-a[u];  //把u的虚儿子的值储存下来,以完成子树Su大小的计算
}
inline void update(int x,int w){  //更新
    int y=x;
    splay(x);
    ll s=sum[x]-sum[ch[x][0]],h=sum[ch[x][1]];
    ans-=calc(x,s,h);
    a[x]+=w;sum[x]+=w;s+=w;
    if((h<<1)<=s){ima[x]+=h;ch[x][1]=0;}
    ans+=calc(x,s,h);
    pushup(x);
    for(x=f[x];x;x=f[y=x]){
        splay(x);
        s=sum[x]-sum[ch[x][0]];h=sum[ch[x][1]];
        ans-=calc(x,s,h);
        sum[x]+=w;ima[x]+=w;s+=w;
        if((h<<1)<=s){
            ima[x]+=h;ch[x][1]=h=0;
            if((sum[y]<<1)>s){  //切换(必须满足新的值可以成为新的实儿子才切换)
                ima[x]-=(h=sum[y]);
                ch[x][1]=y;
            }
        }
        ans+=calc(x,s,h);
        pushup(x);
    }
}
int main(){
    int n=rd(),m=rd(),i,u,v,tot=1;
    for(i=1;i<=n;++i)a[i]=rd();
    for(i=1;i<n;++i){
        u=rd();v=rd();
        add(u,v);
    }
    Q.push(r[1]=1);
    while(!Q.empty()){
        u=Q.front();Q.pop();
        for(i=G[u];i;i=nxt[i])if((v=to[i])!=f[u]){
            f[v]=u;
            r[++tot]=v;
            Q.push(v);
        }
    }
    //求拓扑序
    for(i=n;i;--i)work(r[i]);  //倒序处理,保证u的儿子先于u处理
    wt(ans);
    while(m--){
        u=rd();v=rd();
        update(u,v);
        wt(ans);
    }
    flush();
    return 0;
}
View Code
posted @ 2019-07-29 14:28  wangyuchen  阅读(194)  评论(0编辑  收藏  举报