[ZJOI2018]历史,洛谷P4338,类LCT维护

本文探讨了一种针对有根树的轻重边切换优化算法,旨在通过动态调整访问顺序,最大化轻重边的切换次数。算法首先确定静态最优解,随后通过类似于LCT的维护策略,使用Splay树动态更新树状结构,以应对访问次数的动态变化,确保高效求解。

正题

      题目大意,大致就是给出一棵有根数,给出每个点access的次数,要你安排顺序,求轻重边切换最多多少次,动态加次数.

      第一步其实还挺好想的,思考一下如何静态做,发现相当于对于每一个节点,就是让各个子树的权值和 和 自己的access次数互插,使得相邻两个不同的个数越多越好,这个东西想一想就可以知道答案是\min(2*(n-x),n-1),其中x是最大的个数,具体怎么得来,可以直接分类讨论(x>n/2 x=n/2 x<n/2)三种情况,笔者不多赘述.

      第二步才是有意思的地方,我们可以在x>n/2时连实边,否则连虚边,然后用类似LCT一样维护实边构成的spaly.每次access实边的答案都不会变因为x,n都增加了,虚边可能会变成实边,也有可能将原来的实边断掉,分类讨论即可.这样每次跳虚边,子树权值和必定翻倍,那么总跳跃次数就是log ai总和.每次要进行一次splay,那么复杂度就是两个log.

 

#include<bits/stdc++.h>
#define ls son[x][0]
#define rs son[x][1]
using namespace std;

const int N=450010;
int n,m;
struct edge{
	int y,next;
}s[N<<1];
int first[N],len=0,fa[N],lazy[N],sta[N];
long long sum[N],val[N],S[N];
int son[N][2];
long long ans=0;

void ins(int x,int y){
	s[++len]=(edge){y,first[x]};first[x]=len;
	s[++len]=(edge){x,first[y]};first[y]=len;
}

bool nrt(int x){
	return !(son[fa[x]][0]!=x && son[fa[x]][1]!=x);
}

void update(int x){
	sum[x]=val[x]+S[x]+sum[ls]+sum[rs];
}

void dfs(int x){
	long long mmax=val[x];
	int p=0;
	for(int i=first[x];i!=0;i=s[i].next) if(s[i].y!=fa[x]){
		int y=s[i].y;fa[y]=x;dfs(y);
		S[x]+=sum[y];
		if(sum[y]>mmax) mmax=sum[y],p=y;
	}
	if(2*mmax>=val[x]+S[x]+1 && p) S[x]-=mmax,son[x][1]=p;
	update(x);
	ans+=min(sum[x]-1,2*(sum[x]-mmax));
}

void rotate(int x){
	int f=fa[x],ff=fa[f],w=(son[f][0]==x);
	fa[x]=ff;fa[f]=x;if(son[x][w]) fa[son[x][w]]=f;
	if(son[ff][0]==f) son[ff][0]=x;
	else if(son[ff][1]==f) son[ff][1]=x;
	son[f][1-w]=son[x][w];son[x][w]=f;
	update(f);update(x);
}

void splay(int x){
	while(nrt(x)){
		if(nrt(fa[x])){
			if((son[fa[x]][0]==x) ^ (son[fa[fa[x]]][0]==fa[x])) rotate(x);
			else rotate(fa[x]);
		}
		rotate(x);
	}
}

void access(int x,int t){
	splay(x);
	long long Sum=sum[x]-sum[ls];
	ans-=min(Sum-1,2*(Sum-max(sum[rs],val[x])));
	val[x]+=t;sum[x]+=t;Sum+=t;
	ans+=min(Sum-1,2*(Sum-max(sum[rs],val[x])));
	if(sum[rs]*2<Sum+1) S[x]+=sum[rs],rs=0;
	int last=x;x=fa[x];
	while(x){
		splay(x);
		Sum=sum[x]-sum[ls];
		ans-=min(Sum-1,2*(Sum-max(sum[rs],val[x])));
		sum[x]+=t;S[x]+=t;Sum+=t;
		ans+=min(Sum-1,2*(Sum-max(sum[last],max(val[x],sum[rs]))));
		if(sum[last]*2>=Sum+1) S[x]-=sum[last],S[x]+=sum[rs],rs=last;
		else if(sum[rs]*2<Sum+1) S[x]+=sum[rs],rs=0; 
		last=x;x=fa[x];
	}
}

int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%lld",&val[i]);
	int x,y;
	for(int i=1;i<n;i++) scanf("%d %d",&x,&y),ins(x,y);
	fa[1]=0;dfs(1);
	printf("%lld\n",ans);
	while(m--){
		scanf("%d %d",&x,&y);
		access(x,y);
		printf("%lld\n",ans);
	}
}

      

下载前可以先看下教程 https://pan.quark.cn/s/16a53f4bd595 小天才电话手表刷机教程 — 基础篇 我们将为您简单的介绍小天才电话手表新机型的简单刷机以及玩法,如adb工具的使用,magisk的刷入等等。 我们会确保您看完此教程后能够对Android系统有一个最基本的认识,以及能够成功通过magisk root您的手表,并安装您需要的第三方软件。 ADB Android Debug Bridge,简称,在android developer的adb文档中是这么描述它的: 是一种多功能命令行工具,可让您与设备进行通信。 该命令有助于各种设备操作,例如安装和调试应用程序。 提供对 Unix shell 的访问,您可以使用它在设备上运行各种命令。 它是一个客户端-服务器程序。 这听起来有些难以理解,因为您也没有必要去理解它,如果您对本文中的任何关键名词产生疑惑或兴趣,您都可以在搜索引擎中去搜索它,当然,我们会对其进行简单的解释:是一款在命令行中运行的,用于对Android设备进行调试的工具,并拥有比一般用户以及程序更高的权限,所以,我们可以使用它对Android设备进行最基本的调试操作。 而在小天才电话手表上启用它,您只需要这么做: - 打开拨号盘; - 输入; - 点按打开adb调试选项。 其次是电脑上的Android SDK Platform-Tools的安装,此工具是 Android SDK 的组件。 它包括与 Android 平台交互的工具,主要由和构成,如果您接触过Android开发,必然会使用到它,因为它包含在Android Studio等IDE中,当然,您可以独立下载,在下方选择对应的版本即可: - Download SDK Platform...
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值