UOJ#374. 【ZJOI2018】历史 贪心,LCT
原文链接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的更多相关文章
- [ZJOI2018]历史(LCT)
这篇还发了洛谷题解 [Luogu4338] [BZOJ5212] 题解 题意 给出一棵树,给定每一个点的 \(access\) 次数,计算轻重链切换次数的最大值,带修改. 先考虑不带修改怎么做 假设 ...
- 洛谷P4338 [ZJOI2018]历史(LCT,树形DP,树链剖分)
洛谷题目传送门 ZJOI的考场上最弱外省选手T2 10分成功滚粗...... 首先要想到30分的结论 说实话Day1前几天刚刚刚掉了SDOI2017的树点涂色,考场上也想到了这一点 想到了又有什么用? ...
- BZOJ5212 ZJOI2018历史(LCT)
首先相当于最大化access的轻重边交换次数. 考虑每个点作为战场(而不是每个点所代表的国家与其他国家交战)对答案的贡献,显然每次产生贡献都是该点的子树内(包括自身)此次access的点与上次acce ...
- 【BZOJ5212】[ZJOI2018] 历史(LCT大黑题)
点此看题面 大致题意: 给定一棵树每个节点\(Access\)的次数,求最大虚实链切换次数,带修改. 什么是\(Access\)? 推荐你先去学一学\(LCT\)吧. 初始化(不带修改的做法) 首先考 ...
- 「ZJOI2018」历史(LCT)
「ZJOI2018」历史(LCT) \(ZJOI\) 也就数据结构可做了-- 题意:给定每个点 \(access\) 次数,使轻重链切换次数最大,带修改. \(30pts:\) 挺好想的.发现切换次数 ...
- [ZJOI2018]历史
[ZJOI2018]历史 最大化access轻重链的切换次数 考虑一个点的贡献,即它交换重儿子的次数 发现这个次数只和它自己ai以及每个儿子的子树次数和有关. 一个关键的事实是: 我们可以自上而下进行 ...
- 【BZOJ5212】[ZJOI2018]历史(Link-Cut Tree)
[BZOJ5212][ZJOI2018]历史(Link-Cut Tree) 题面 洛谷 BZOJ 题解 显然实际上就是给定了一棵树和每个点被\(access\)的次数,求解轻重链切换的最大次数. 先考 ...
- Luogu4338 ZJOI2018 历史 LCT、贪心
传送门 题意:在$N$个点的$LCT$中,最开始每条边的虚实不定,给出每一个点的$access$次数,求一种$access$方案使得每条边的虚实变换次数之和最大,需要支持动态增加某个点的$access ...
- 【刷题】UOJ #374 【ZJOI2018】历史
九条可怜是一个热爱阅读的女孩子. 这段时间,她看了一本非常有趣的小说,这本小说的架空世界引起了她的兴趣. 这个世界有 \(n\) 个城市,这 \(n\) 个城市被恰好 \(n-1\) 条双向道路联通, ...
随机推荐
- Istio
什么是Istio Istio是Service Mesh(服务网格)的主流实现方案.该方案降低了与微服务架构相关的复杂性,并提供了负载均衡.服务发现.流量管理.断路器.监控.故障注入和智能路由等功能特性 ...
- js实现点气球小游戏
二话不说直接贴代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...
- axios传参
get //通过给定的ID来发送请求 axios.get('/user?ID=12345') .then(function(response){ console.log(response); }).c ...
- <HTML> 模块
一些元素 <q>元素 用于引用, quote 浏览器一般会负责加上双引号,但是不是所有的浏览器都会在<q>元素的内容两边加双引号. <blockquote>元素 用 ...
- SOA 和 微服务
正在读 钟华 著的<<企业IT架构转型之道 - 阿里巴巴中台战略思想与架构实战>> 一书, 参考了网上的讨论, 对SOA和微服务有了一些新的认识. 知乎上的讨论: SOA 与 ...
- 【OpenGL】代码记录01创建窗口
创建空窗口: #include<iostream> // GLEW #define GLEW_STATIC #include <GL/glew.h> // GLFW #incl ...
- XGBoost参数调优完全指南
简介 如果你的预测模型表现得有些不尽如人意,那就用XGBoost吧.XGBoost算法现在已经成为很多数据工程师的重要武器.它是一种十分精致的算法,可以处理各种不规则的数据.构造一个使用XGBoost ...
- AngularJs实现表单验证
首先,我们应该知道,表单中,常用的验证操作有: $dirty 表单有填写记录 $valid 字段内容合法的 $invalid 字段内容是非法的 $pristine 表单没有填写记录 $error 表单 ...
- webpack4.0学习记录
2019/04/28 1.本质上,webpack基于node node跟webpack为最新稳定版,才能更好,更快的打包 安装 1.卸载node 直接在控制面板 卸载 2.安装 从官网下载 然后 ...
- insert主键返回 selectKey使用
有时候新增一条数据,知道新增成功即可,但是有时候,需要这条新增数据的主键,以便逻辑使用,再将其查询出来明显不符合要求,效率也变低了. 这时候,通过一些设置,mybatis可以将insert的数据的主键 ...