• [洛谷P4338][题解][ZJOI2018]历史


    0.Description

    原题面
    简化版题意:给定每个点access次数(存在单点增加),求虚实边切换最大次数。

    1.Solution

    这道题看起来就是一个 LCT,但是它没有 LCT 常见的连边删边等操作,而是着重考察了对于access函数的理解与灵活运用。
    观察题意,我们不难发现节点(u)对答案的贡献仅与它的子树有关:只要有和之前不同的儿子崛起,(u)就会改变。比较细心的同学可能已经发现它可以抽象成一个模型:给定(m)种颜色的球和出现次数(cnt_i),求最多有多少组相邻不同色球。
    而这个模型有一个固定的解法:令(t=sum cnt_i,h=max{cnt_i}),则最终答案为(min{2(t-h),t-1}),也就是说当(2h>t)时答案为(2(t-h)),否则为(t-1)。这个式子有一个很好理解的解释:如果最多的球不过半,那么我们总能找到一种方案使得所有球都交替放;如果最多的球过半,我们就将剩余的球不相邻地插入最多的球,每插入一个贡献(2)的答案。
    由此,我们就有了一种确定的方法来计算子树的贡献:如果在(u)的儿子中出现了(2*siz[v]>siz[u])的情况(以下简称垄断颜色),我们就连实边,否则连虚边。可以发现,每个点至多有一条实边且实边的贡献类型固定为(2(t-h))

    接下来我们考虑:LCT 中需要维护什么信息?
    对于一般的信息更新siz[k]=siz[ls]+siz[rs]+val[k],它维护的是 Splay 中的信息,现在我们想要维护原树中子树的信息,就需要多用一个vs[k]来维护原树中虚边的信息(这些信息是不记入 Splay 的),更新时则变为siz[k]=siz[ls]+siz[rs]+val[k]+vs[k]

    接下来就到了最为关键的access操作,由于我们发现虚边的条数为(mathcal{O(log a_i)})(走虚边则值减半),所以可以考虑access跳虚边时暴力维护。维护时先将答案减去原值,然后注意垄断节点的变化,更新完毕后加回答案,有亿些细节可以看代码(我不会告诉你我把题解代码手抄下来回宿舍理解了一中午的(但是理解之后确实很快过了))。另外,LCT 中的一些信息(比如vssiz)和初始答案需要 DFS 时预处理出来。

    2.Code

    第二个点就爆int,你好狠心啊

    const int N=400010;
    int n,m,a[N],ans;
    struct Edge {
    	int to,nxt;
    }e[N<<1];
    int hd[N],cnt;
    il void ade(int u,int v){
    	e[++cnt].to=v,e[cnt].nxt=hd[u],hd[u]=cnt;
    }
    int ch[N][2],fa[N],siz[N],vs[N]; 
    il int Son(int k){
    	return ch[fa[k]][1]==k;
    }
    il bool IsRoot(int k){
    	return ch[fa[k]][0]!=k&&ch[fa[k]][1]!=k;
    }
    il void Pushup(int k){
    	siz[k]=siz[ls]+siz[rs]+a[k]+vs[k];
    }
    il void Rotate(int k){
    	int fk=fa[k],gfk=fa[fk];
    	int b=Son(k),c=Son(fk),a=ch[k][!b];
    	if(!IsRoot(fk))ch[gfk][c]=k;fa[k]=gfk;
    	ch[fk][b]=a,fa[a]=fk;
    	ch[k][!b]=fk,fa[fk]=k;
    	Pushup(fk),Pushup(k);
    }
    il void Splay(int k){
    	while(!IsRoot(k)){
    		int fk=fa[k];
    		if(!IsRoot(fk)){
    			if(Son(k)==Son(fk))Rotate(k);
    			else Rotate(fk);
    		}
    		Rotate(k);
    	}
    }
    il int Calc(int k,int t,int h){
    	//先判断子树内垄断颜色,再判断自己 
    	return rs?2*(t-h):(2*a[k]>t?2*(t-a[k]):t-1);
    }
    il void Access(int k,int w){
    	Splay(k);
    	int sum=siz[k]-siz[ls],mx=siz[rs];
    	ans-=Calc(k,sum,mx);
    	siz[k]+=w,a[k]+=w,sum+=w;//更新 
    	if((mx<<1)<=sum)vs[k]+=mx,rs=mx=0;//垄断节点没了,断实边 
    	ans+=Calc(k,sum,mx),Pushup(k);
    	int son=k;k=fa[k];
    	for(;k;son=k,k=fa[k]){//向上跳虚边 
    		Splay(k);
    		int sum=siz[k]-siz[ls],mx=siz[rs];//同上 
    		ans-=Calc(k,sum,mx);
    		siz[k]+=w,vs[k]+=w,sum+=w;//由于k是由虚边而来,所以应该更新vs 
    		if((mx<<1)<=sum)vs[k]+=mx,rs=mx=0;
    		if((siz[son]<<1)>sum)vs[k]-=(mx=siz[son]),rs=son;//出现垄断节点,连实边 
    		ans+=Calc(k,sum,mx),Pushup(k);
    	}
    }
    void DFS(int u,int ff){
    	siz[u]=a[u],fa[u]=ff;
    	for(rg int i=hd[u];i;i=e[i].nxt){
    		int v=e[i].to;
    		if(v!=ff)DFS(v,u),siz[u]+=siz[v];
    	}
    	for(rg int i=hd[u];i;i=e[i].nxt){
    		int v=e[i].to;
    		if(v!=ff&&2*siz[v]>siz[u])ch[u][1]=v;
    	}
    	vs[u]=siz[u]-a[u]-siz[ch[u][1]];
    	ans+=Calc(u,siz[u],siz[ch[u][1]]);
    }
    signed main(){
    	Read(n),Read(m);
    	for(rg int i=1;i<=n;i++)Read(a[i]);
    	for(rg int i=1,u,v;i<n;i++){
    		Read(u),Read(v),ade(u,v),ade(v,u);
    	}
    	DFS(1,0);
    	cout<<ans<<endl;
    	for(rg int i=1,xi,wi;i<=m;i++){
    		Read(xi),Read(wi),Access(xi,wi);
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    

    3.Ending

    本来想写个 LCT 总结来着,后来发现自己太菜写不出,网上也已经有了不少优质教学,正好自己很久没写单题题解了,于是水了这样一篇博客。

    内容来自_ajhfff_的博客(https://www.cnblogs.com/juruoajh/),未经允许,不得转载。
  • 相关阅读:
    登录注册功能
    29-----BBS论坛
    linux笔记
    nginx,uwsgi发布web服务器
    linux常用服务部署
    linux系统基础优化及常用命令
    linux基本操作命令
    linux命令
    linux基础
    阿里云服务器搭建
  • 原文地址:https://www.cnblogs.com/juruoajh/p/14490690.html
Copyright © 2020-2023  润新知