• 【ZJOI2018】历史【LCT】【树形DP】


    Decription

    • 题目链接

    • 给定一棵树以及树上的每个节点的(access)次数,请合理安排(access)顺序以最大化虚实链的切换次数,需要支持单点修改。

    Solution

    首先考虑不带修改的情况:

    对于节点(u)(u)点的虚实链切换次数至于(u)子树内的点有关,每个点是独立的,可以分开计算。而(u)节点的每次切换一定意味着出现了先后两个(access)来自(u)不同的子树,考虑最优策略,设(mx)(u)的每个子树的(access)次数中最大的一个,(sum)为它们(access)次数的总和。

    那么当(2mxleq sum)时,我们总能找到方法使得任意相邻的两个(access)都来自不同子树,贡献为(sum-1),否则最后几个只能是连续的(mx)了,贡献为(2(sum-mx)),因此总贡献就是(min(2(sum-mx),sum-1)),于是直接树形(DP)一遍即可。

    接着考虑带修改的情况:

    修改节点(u)时,显然只会影响它的祖先的贡献。

    对于节点(u)以及它的儿子(v),如果(2s_v)((s_u)(u)子树的(access)次数之和)(> s_u),因此(u)的贡献为(2(s_u-s_v)),那么接下来如果(v)子树中的点进行修改,增加了(w)(2(s_u-s_v))依然比(sum-1)大,(u)的贡献不会改变。这种情况下从(u)(v)连一条实边,其他边为虚边,于是我们修改节点(x)时只会影响其到根路径上所有虚边的顶点的答案,也只会有可能将这些虚边变为实边。

    而每条路径上的虚边数量一定(<log sum a),证明与树剖类似,至于找虚边则直接(access)即可。

    Code

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=4e5+10;
    int n,m,first[N],cnt,fa[N];
    ll a[N],s[N],vs[N],ans;
    struct node{
    	int v,nxt;
    }e[N<<1];
    inline void add(int u,int v){e[++cnt].v=v;e[cnt].nxt=first[u];first[u]=cnt;}
    
    int ch[N][2];
    inline bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
    inline bool which(int x){return ch[fa[x]][1]==x;}
    inline void pushup(int u){s[u]=vs[u]+s[ch[u][0]]+s[ch[u][1]]+a[u];}
    inline void Rotate(int x){
    	int y=fa[x],z=fa[y],wx=which(x),wy=which(y);
    	fa[x]=z;if(!isroot(y)) ch[z][wy]=x;
    	ch[y][wx]=ch[x][wx^1];fa[ch[x][wx^1]]=y;
    	ch[x][wx^1]=y;fa[y]=x;
    	pushup(y);pushup(x);
    }
    inline void Splay(int x){
    	while(!isroot(x)){
    		int y=fa[x];
    		if(!isroot(y)) which(x)==which(y)?Rotate(y):Rotate(x);
    		Rotate(x);
    	}
    	pushup(x);
    }
    
    inline void solve(int u){
    	s[u]=a[u];
    	ll mx=a[u],pos=0;
    	for(int i=first[u];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(v==fa[u]) continue;
    		fa[v]=u;solve(v);
    		s[u]+=s[v];
    		if(s[v]>mx) mx=s[v],pos=v; 
    	}
    	ans+=min(s[u]-1,(s[u]-mx)<<1);
    	if((mx<<1)>=s[u]+1) ch[u][1]=pos;
    	vs[u]=s[u]-a[u]-s[ch[u][1]];
    }
    inline ll calc(int u,ll sum,ll mx){
    	return mx?(sum-mx)<<1:((a[u]<<1)>=sum+1)?(sum-a[u])<<1:sum-1; 
    }
    inline void access(int x,ll w){
    	Splay(x);
    	ll sum=s[x]-s[ch[x][0]],mx=s[ch[x][1]];
    	ans-=calc(x,sum,mx);	
    	a[x]+=w;sum+=w;
    	if((mx<<1)<sum+1) ch[x][1]=0,vs[x]+=mx,mx=0;
    	pushup(x);ans+=calc(x,sum,mx);
    	int last=x;x=fa[x];
    	for(;x;last=x,x=fa[x]){
    		Splay(x);
    		ll sum=s[x]-s[ch[x][0]],mx=s[ch[x][1]];
    		ll rec=ans;
    		ans-=calc(x,sum,mx);	
    		s[x]+=w;sum+=w;vs[x]+=w;
    		if((mx<<1)<sum+1) ch[x][1]=0,vs[x]+=mx,mx=0;
    		if((s[last]<<1)>=sum+1) ch[x][1]=last,vs[x]-=s[last],mx=s[last];
    		pushup(x);ans+=calc(x,sum,mx);
    	}
    }
    
    此处省略了一个iobuff 
    
    int main(){
    //	freopen("b4.in","r",stdin);
    	read(n);read(m);
    	for(int i=1;i<=n;++i) read(a[i]);
    	for(int i=1,u,v;i<n;++i) read(u),read(v),add(u,v),add(v,u);
    	solve(1);
    	putint(ans,'
    ');ll w;
    	for(int i=1,x;i<=m;++i){
    		read(x);read(w);
    		access(x,w);
    		putint(ans,'
    ');
    	}
    	flush();
    	return 0;
    } 
    
  • 相关阅读:
    转载:SQL Server错误 2812 :未能找到存储过程 ***的解决方法
    转载:mysql5.7设置不区分大小写
    无法对数据库'XXX' 执行删除,因为它正用于复制"
    Win7 64位 部分软件和文档字体显示乱码
    转载:创建对于用户sa失败,sa无法映射到数据库
    sqlserver 用户角色权限
    转发:Nginx可视化配置工具—NginxWebUI
    docker-compose安装
    导入导出windows 防火墙规则
    python 打包pyinstaller 问题记录
  • 原文地址:https://www.cnblogs.com/tqxboomzero/p/14555720.html
Copyright © 2020-2023  润新知