原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ374.html

题解

想出正解有点小激动。

不过因为傻逼错误调到自闭。不如贺题

首先我们考虑如何 $O(n)$ 求一个答案。

首先,计算两条路径的贡献时,由于两国连续交战数次只算一次,所以我们可以只看这两条路径的交的最深点。

也就是说,我们可以对于每一个点分开考虑,假装他的同一个子树的所有点颜色相同,不同子树的点颜色不同,它本身也当作一个子树看。

假设 x 是当前节点,y 是 x 的子树。

设 size[v] 表示 v 子树的所有节点的 a[v] 之和。

那么我们容易推出两个断论:

1. x 节点对答案的贡献最多不超过 size[x] - 1 。

2. 设 max(size[y]) 表示 x 的所有子树中 size 最大的子树的 size ,当 max(size[y]) - 1 >= size[x] - max(size[y]) 时,都有使 x 的贡献为 size[x] - 1 的方案;否则, x 节点对答案的贡献最大为 max(size[y]) - 1 - (size[x] - max(size[y])) = 2max(size[y]) - 1 - size[x]

所以贡献为

$$min(size[x] - 1, 2max(size[y]) - 1 - size[x])$$

设 val[x] = size[x] - 1 ,可以证明 $\sum_{y} val[y] \leq \sum_{y} (size[y] - 1) \leq size[x] - 1 = val[x]$

则这个式子会更加好看(把常数消掉了,然并卵):

$$min(val[x],2max(val[y])-val[x])$$

现在已经可以轻松拿到 30 分了。

考虑 100 分怎么做。

我们可以发现好像操作的时候所有的 max(val[y]) 的 y 的变化次数不多啊!

于是我们可以想到 LCT 维护这个东西。

这里的 LCT 不是传统的 LCT 。

如果 val[x] >= val[fa[x]] 那么我们将 x 作为 fa[x] 的重儿子。我们可以发现每一个节点只有一个重儿子:由于 $\sum_{y} val[y] \leq val[x]$ ,而且两个子树的特殊情况特殊考虑一下发现也是对的。

这样的话,可能会有节点没有重儿子。

但是,从任意一个节点到根走过的轻边条数是 $O(\log \sum a[i])$ 的,因为每走过一条轻边,子树权值和至少翻一倍。

然后你发现修改一个点的时候只要修改它到根路径上的所有点权(val[x]),而且对于重链,它对答案的贡献是不变的!

所以只要对 $O(\log\sum a[i])$ 个轻边处理就好了。

由于要链上修改点权,所以每一段重链都要预先下传标记。

总的来说,这样做要跳过 $O(\log \sum a[i])$ 段重链,每段重链 splay 需要花费 $O(\log n)$ 的时间复杂度,所以看上去复杂度是 $O(n\log^2 n)$ 的。80分很开心了吧!更开心的是如果交上去的话它能 AC 。

这是为什么呢?我们考虑势能分析,定义势函数为 $\sum_{ LCT 上所有节点 }\ \ \ \ \ \log (该节点在splay结构上的size + 它的虚子树的size)$ ,类似于 splay 复杂度的证明,可以证明这个东西是均摊 $O(\log \sum a[i] + \log n)$ 的。

这里不把证明写出来了。懒得写了。

最终时间复杂度为 $O(n\log(n\sum a[i]))$ 。

注意在写代码的时候要注意一些细节。对于节点本身的贡献我们可以把每一个点拆成两个点,第一个点先连原先所有子树,再新建第二个点,让他们连起来,并使第一个点是第二个点的父亲,第二个点的权值为 a[x] - 1 。这样可以减掉几个 if 。

注意链上修改的时候,不是直接给根打标记就完事了,因为这里的 LCT 比较奇怪,所以直接打标记会多给一段后缀重链带来修改,所以我们还要再在这个后缀重链上打个标记来抵消根上的标记。

代码

#pragma GCC optimize("Ofast","inline")
#include <bits/stdc++.h>
#define clr(x) memset(x,0,sizeof (x))
#define For(i,a,b) for (int i=a;i<=b;i++)
#define Fod(i,b,a) for (int i=b;i>=a;i--)
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define _SEED_ ('C'+'L'+'Y'+'A'+'K'+'I'+'O'+'I')
#define outval(x) printf(#x" = %d\n",x)
#define outvec(x) printf("vec "#x" = ");for (auto _v : x)printf("%d ",_v);puts("")
#define outtag(x) puts("----------"#x"----------")
using namespace std;
typedef long long LL;
LL read(){
LL x=0,f=0;
char ch=getchar();
while (!isdigit(ch))
f|=ch=='-',ch=getchar();
while (isdigit(ch))
x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return f?-x:x;
}
const int N=400005*2;
int n,m;
LL a[N],s[N],v[N],f[N];
vector <int> e[N];
LL ans=0;
void dfs(int x,int pre){
f[x]=pre;
s[x]=a[x];
LL Mx=a[x]-1;
for (auto y : e[x])
if (y!=pre){
dfs(y,x);
s[x]+=s[y];
Mx=max(Mx,v[y]);
}
v[x]=s[x]-1;
ans+=min(v[x],(v[x]-Mx)*2);
}
int fa[N],son[N][2];
LL val[N],Add[N],Mxv[N];
void LCT_build(){
clr(son),clr(val),clr(Add);
For(i,1,n){
fa[i]=i+n,val[i]=a[i]-1;
fa[i+n]=f[i]?f[i]+n:0,val[i+n]=v[i];
}
For(i,1,n*2){
Mxv[i]=val[i];
if (fa[i]&&val[i]*2>=val[fa[i]])
son[fa[i]][1]=i;
}
}
#define ls son[x][0]
#define rs son[x][1]
int isroot(int x){
return son[fa[x]][0]!=x&&son[fa[x]][1]!=x;
}
int wson(int x){
return son[fa[x]][1]==x;
}
void pushup(int x){
Mxv[x]=max(val[x],max(Mxv[ls],Mxv[rs]));
}
void pushdown(int x){
if (Add[x]){
if (ls)
val[ls]+=Add[x],Add[ls]+=Add[x],Mxv[ls]+=Add[x];
if (rs)
val[rs]+=Add[x],Add[rs]+=Add[x],Mxv[rs]+=Add[x];
Add[x]=0;
}
}
void pushadd(int x){
if (!isroot(x))
pushadd(fa[x]);
pushdown(x);
}
void rotate(int x){
if (isroot(x))
return;
int y=fa[x],z=fa[y],L=wson(x),R=L^1;
if (!isroot(y))
son[z][wson(y)]=x;
fa[x]=z,fa[y]=x,fa[son[x][R]]=y;
son[y][L]=son[x][R],son[x][R]=y;
pushup(y),pushup(x);
}
void splay(int x){
pushadd(x);
for (int y=fa[x];!isroot(x);rotate(x),y=fa[x])
if (!isroot(y))
rotate(wson(x)==wson(y)?y:x);
}
void False_Access(int x){//pushdown the tags
while (x)
splay(x),x=fa[x];
}
void update(int x,LL w){
False_Access(x);
if (rs)
val[rs]-=w,Add[rs]-=w,Mxv[rs]-=w;
while (fa[x]){
int y=fa[x];
if (son[y][1]){
if (val[y]+w>Mxv[son[y][1]]*2){
ans+=val[y]+w-(val[y]-Mxv[son[y][1]])*2;
son[y][1]=0;
}
else
ans+=w*2;
}
else
ans+=w;
if ((Mxv[x]+w)*2>val[y]+w){
ans+=(val[y]+w-(Mxv[x]+w))*2-(val[y]+w);
son[y][1]=x;
}
else {
val[x]+=w,Add[x]+=w,Mxv[x]+=w;
if (son[y][1])
val[son[y][1]]-=w,Add[son[y][1]]-=w,Mxv[son[y][1]]-=w;
}
x=y;
}
val[x]+=w,Add[x]+=w,Mxv[x]+=w;
}
#undef ls
#undef rs
int main(){
n=read(),m=read();
For(i,1,n)
a[i]=read();
For(i,1,n-1){
int x=read(),y=read();
e[x].pb(y),e[y].pb(x);
}
dfs(1,0);
printf("%lld\n",ans);
LCT_build();
For(i,1,m){
int x=read(),w=read();
update(x,w);
printf("%lld\n",ans);
}
return 0;
}

  

UOJ#374. 【ZJOI2018】历史 贪心,LCT的更多相关文章

  1. [ZJOI2018]历史(LCT)

    这篇还发了洛谷题解 [Luogu4338] [BZOJ5212] 题解 题意 给出一棵树,给定每一个点的 \(access\) 次数,计算轻重链切换次数的最大值,带修改. 先考虑不带修改怎么做 假设 ...

  2. 洛谷P4338 [ZJOI2018]历史(LCT,树形DP,树链剖分)

    洛谷题目传送门 ZJOI的考场上最弱外省选手T2 10分成功滚粗...... 首先要想到30分的结论 说实话Day1前几天刚刚刚掉了SDOI2017的树点涂色,考场上也想到了这一点 想到了又有什么用? ...

  3. BZOJ5212 ZJOI2018历史(LCT)

    首先相当于最大化access的轻重边交换次数. 考虑每个点作为战场(而不是每个点所代表的国家与其他国家交战)对答案的贡献,显然每次产生贡献都是该点的子树内(包括自身)此次access的点与上次acce ...

  4. 【BZOJ5212】[ZJOI2018] 历史(LCT大黑题)

    点此看题面 大致题意: 给定一棵树每个节点\(Access\)的次数,求最大虚实链切换次数,带修改. 什么是\(Access\)? 推荐你先去学一学\(LCT\)吧. 初始化(不带修改的做法) 首先考 ...

  5. 「ZJOI2018」历史(LCT)

    「ZJOI2018」历史(LCT) \(ZJOI\) 也就数据结构可做了-- 题意:给定每个点 \(access\) 次数,使轻重链切换次数最大,带修改. \(30pts:\) 挺好想的.发现切换次数 ...

  6. [ZJOI2018]历史

    [ZJOI2018]历史 最大化access轻重链的切换次数 考虑一个点的贡献,即它交换重儿子的次数 发现这个次数只和它自己ai以及每个儿子的子树次数和有关. 一个关键的事实是: 我们可以自上而下进行 ...

  7. 【BZOJ5212】[ZJOI2018]历史(Link-Cut Tree)

    [BZOJ5212][ZJOI2018]历史(Link-Cut Tree) 题面 洛谷 BZOJ 题解 显然实际上就是给定了一棵树和每个点被\(access\)的次数,求解轻重链切换的最大次数. 先考 ...

  8. Luogu4338 ZJOI2018 历史 LCT、贪心

    传送门 题意:在$N$个点的$LCT$中,最开始每条边的虚实不定,给出每一个点的$access$次数,求一种$access$方案使得每条边的虚实变换次数之和最大,需要支持动态增加某个点的$access ...

  9. 【刷题】UOJ #374 【ZJOI2018】历史

    九条可怜是一个热爱阅读的女孩子. 这段时间,她看了一本非常有趣的小说,这本小说的架空世界引起了她的兴趣. 这个世界有 \(n\) 个城市,这 \(n\) 个城市被恰好 \(n-1\) 条双向道路联通, ...

随机推荐

  1. 第六周java学习总结

    学号 20175206 <Java程序设计>第六周学习总结 教材学习内容总结 第七章: 主要内容 内部类 匿名类 异常类 断言 重点和难点 重点:内部类和异常类的理解 难点:异常类的使用 ...

  2. ubuntu 默认 root 密码

    安装完Ubuntu后忽然意识到没有设置root密码,不知道密码自然就无法进入根用户下.到网上搜了一下,原来是这麽回事.Ubuntu的默认root密码是随机的,即每次开机都有一个新的root密码.我们可 ...

  3. Linux之vi编辑器

    vi编辑器是Linux系统下标准的编辑器.而且不逊色于其他任何最新的编辑器.可是会用的有多少呢.下面介绍一下vi编辑器的简单用法和部分命令.让你在Linux系统中畅行无阻. 基本上vi可以分为三种状态 ...

  4. 2017-12-20python全栈9期第五天第二节之字典的增删查改和字典的for循环

    #!/user/bin/python# -*- coding:utf-8 -*-dic1 = {'age':24,'name':'zd','sex':'boy'}print(dic1)#增dic1[' ...

  5. 06--STL序列容器(priority_queue)

    一:优先队列priority_queue简介 同队列,不支持迭代 (一)和队列相比 同: 优先队列容器与队列一样,只能从队尾插入元素,从队首删除元素. 异: 但是它有一个特性,就是队列中最大的元素总是 ...

  6. .net core引用错误的Entity Framework而导致不能正常迁移数据的解决办法

    本人刚学.net core,因此在学习过程中会遇上许许多多的坑.每一位初学者最大的问题在于资料的查看不仔细或是没有正确理解里面的内容,导致在后面自己在不知道错误的情况下做了一个小动作.对于完全没有理解 ...

  7. 基于jeesite的cms系统(四):使用Beetl模版引擎在后端渲染数据

    一.Beetl简介 1. 什么是Beetl Beetl目前版本是2.9.3,相对于其他java模板引擎,具有功能齐全,语法直观,性能超高,以及编写的模板容易维护等特点.使得开发和维护模板有很好的体验. ...

  8. PMP知识点(四)——项目管理计划的内容

    项目管理计划([4.2制定项目管理计划]的输出) 包含三个基准和十三个子计划和一些其他内容 三个基准:成本基本.进度基准.范围基准 其中范围基准([5.4创建WBS]的输出)包含了:项目范围说明书.W ...

  9. navicat连接IEE数据库查询话单

    select * from cdl_raw_1x_12501_ztev8_sht_201811 t1 WHERE ( t1.call_start_time >= STR_TO_DATE( '20 ...

  10. 最短路径(Dijkstra算法)

    算法局限性:边的权值不能为负. 需要两个辅助数组dist[],path[],分别记录起点到各点的最短距离和最短路径 算法步骤: 1.根据起点v0初始化dist[]和path[]数组. 2.在剩下的点中 ...