P4338-[ZJOI2018]历史【LCT】

1|0正题

题目链接:https://www.luogu.com.cn/problem/P4338


1|1题目大意

给出n个点的一棵树,和每个点进行access的次数ai,要求安排一个顺序使得虚实边转换最多。

m次修改一个点让ai加上w后求答案

n,m[1,4105],ai,w[1,107]


1|2解题思路

好像本来就很麻烦还带修改,那先不考虑修改

考虑统计每个节点下的边的虚实切换最大化,可以发现一个节点的子树中无论怎样安排access顺序也不会影响外面的答案,因为对于外面的来说都相当于access了这个节点。

所以这个满足子最优?(好像是这么叫的),那每一个节点的分块考虑就好了。现在对于这个节点下的边,如果两次access的是在不同的儿子的子树中就会产生一点贡献。

转换一下现在的问题就是有若干种个数不同的颜色排成一排,要求相邻的异色最多。这个可以贪心解决,正常来说只要每次拿与上个不同的最多的来排就能到达sum1的答案上线,但是需要特判一下如果最多的颜色个数mx2×mx>sum那么此时这样排到最后还有一种颜色剩下,答案就是2×(summx)

所以一个节点的答案就是min{sum1,2×(summx)}

但是带修改怎么搞,考虑到每次修改一定是加一个正权。

我们显然有一个式子

2×mx>sum2×(mx+c)>sum+c

这个式子表明如果一个节点选择了2×(summx)作为权值,那么它以后也都是这个权值。

sx表示x子树中的权值和,对一个节点x定义r=max{sy(fay=x)}sum=fay=xsy

如果2×mx>sum那么向(x,y)连一条实边,其他儿子连虚边。否则全连虚边。

那么每次修改的过程中我们只需要遍历到根节点的虚边看是否需要切换即可,这个可以用LCT来维护。

而且因为每条虚边代表着2×sysx,所以路径上虚边的个数不会超过logai级别。

时间复杂度O(nlogai)Splay那个logn因为小于logai就舍去)


1|3code

#include<cstdio> #include<cstring> #include<algorithm> #define ll long long using namespace std; const ll N=4e5+10; struct node{ ll to,next; }a[N<<1]; ll n,m,s[N],ls[N],ans,tot; struct LCT{ ll fa[N],s[N],w[N],v[N],t[N][2]; bool Nroot(ll x) {return fa[x]&&(t[fa[x]][0]==x||t[fa[x]][1]==x);} bool Direct(ll x){return t[fa[x]][1]==x;} void PushUp(ll x) {s[x]=s[t[x][0]]+s[t[x][1]]+w[x]+v[x];return;} void Rotate(ll x){ ll y=fa[x],z=fa[y]; ll xs=Direct(x),ys=Direct(y); ll w=t[x][xs^1]; t[x][xs^1]=y;t[y][xs]=w; if(Nroot(y))t[z][ys]=x; fa[x]=z;fa[y]=x;if(w)fa[w]=y; PushUp(y);PushUp(x); return; } void Splay(ll x){ while(Nroot(x)){ ll y=fa[x]; if(!Nroot(y))Rotate(x); else if(Direct(x)==Direct(y)) Rotate(y),Rotate(x); else Rotate(x),Rotate(x); } return; } ll ct(ll x,ll r,ll h){ if(t[x][1])return (r-h)*2; return min(r-1,(r-v[x])*2); } void Access(ll x,ll c){ Splay(x); ll r=s[x]-s[t[x][0]],h=s[t[x][1]]; ans-=ct(x,r,h);v[x]+=c;r+=c;PushUp(x); if(h*2<r+1)w[x]+=h,t[x][1]=0; ans+=ct(x,r,h);PushUp(x);ll y; for(y=x,x=fa[x];x;y=x,x=fa[x]){ Splay(x); ll r=s[x]-s[t[x][0]],h=s[t[x][1]]; ans-=ct(x,r,h);w[x]+=c;r+=c; if(h*2<r+1)w[x]+=h,t[x][1]=h=0; if(s[y]*2>r)w[x]-=s[y],t[x][1]=y,h=s[y]; ans+=ct(x,r,h);PushUp(x); } return; } }T; void addl(ll x,ll y){ a[++tot].to=y; a[tot].next=ls[x]; ls[x]=tot;return; } void dp(ll x,ll fa){ ll son=0,mx=T.v[x]=s[x];T.fa[x]=fa; for(ll i=ls[x];i;i=a[i].next){ ll y=a[i].to; if(y==fa)continue; dp(y,x);s[x]+=s[y]; if(s[y]>mx)son=y,mx=s[y]; } ans+=min(s[x]-1,(s[x]-mx)*2); if(mx*2>s[x])T.t[x][1]=son; T.w[x]=s[x]-T.v[x]-s[T.t[x][1]]; T.s[x]=s[x];return; } signed main() { scanf("%lld%lld",&n,&m); for(ll i=1;i<=n;i++) scanf("%lld",&s[i]); for(ll i=1;i<n;i++){ ll x,y; scanf("%lld%lld",&x,&y); addl(x,y);addl(y,x); } dp(1,0);printf("%lld\n",ans); for(ll i=1;i<=m;i++){ ll x,w; scanf("%lld%lld",&x,&w); T.Access(x,w); printf("%lld\n",ans); } return 0; }

__EOF__

本文作者QuantAsk
本文链接https://www.cnblogs.com/QuantAsk/p/14386293.html
关于博主:退役OIer,GD划水选手
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   QuantAsk  阅读(80)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示