大致题意: 给定一棵树每个节点(Access)的次数,求最大虚实链切换次数,带修改。
什么是(Access)?
推荐你先去学一学(LCT)吧。
初始化(不带修改的做法)
首先考虑初始化,即不带修改的做法,貌似这样就有30分了。
先要注意到一点:我们可以发现,对于每一个节点,它的答案是独立的,且只受其子树内的节点影响。
这么一说,应该就不难想到树形(DP)了吧。
如果有两个相邻(Access)操作,显然当且仅当这两次(Access)来自于当前节点的两个不同子节点的子树,才会对答案造成贡献。
那么如何快速求出答案呢?
设(tot_i)为以(i)为根的子树内的(Access)操作总次数,(Sum=sum tot_{son}),(Max=max{tot_{son}})。
则对于当前节点的答案有两种情况:
-
(2*Maxle Sum)。
则显然在最优情况下我们可以每次选择两个来自不同子树的节点作为相邻的(Access)节点,因此每一次(Access)都会对答案造成贡献。因此此时的答案就是(Sum-1)。
例如:(tot_x=3,tot_y=4,tot_z=5),我们可以这样操作:(xyxzxzyzyzyz)。
-
(2*Max>Sum)。
此时我们每次必然先每次选择一个来自非(Max)节点子树内的节点与一个来自(Max)节点子树内的节点作为相邻的(Access)节点,然后将多余的来自(Max)节点子树内的节点全部放在最后面,这样一来造成贡献就是非(Max)节点子树内的(Access)操作总次数(*2),即(2(Sum-Max))。
例如:(tot_x=2,tot_y=3,tot_z=7),我们可以这样操作:(xzxzyzyzyzzz)。易发现只有前面(10)次操作有贡献。
这样,我们就可以完成初始化了。
考虑修改
既然涉及到(Access)操作,我们自然要用(LCT)来做这道题啦... ...
只不过这里的(LCT)的(Access)函数要加上一大堆的判断。
这里要给每个节点维护(3)个值:(Val,Sum,Calc)。其中(Val)存储当前节点的(Access)次数,(Sum)存储以在实际树中以当前节点为根的子树和在(Splay)中以当前节点的左儿子为根的子树的(Access)操作总数(包括当前节点),(Calc)存储在实际树中以当前节点为根的子树除去在(Splay)中以当前节点的右儿子为根的子树后的(Access)操作总数。
这样一来,(PushUp)时就可以得到当前节点的(Sum)就等于其左右儿子(Sum)之和加上当前节点的(Val)和(Calc)。
对于每一个节点,我们将其向(Access)操作总数(*2>)当前节点(Access)操作总数的子节点连一条实边(若没有则不连),其余节点连虚边。
下面,让我们来考虑如何(Access)。
我们需要记三个变量:(x)表示当前操作节点,(son)表示上一个操作节点,(val)表示当前增加的(Access)次数。
首先,将(x)先(Splay)到根。
然后,我们用一个变量(Sum)存下(x)的(Sum)值减去其左儿子的(Sum)值,这样一来就得到实际树中以(x)为根的子树的(Access)操作总数了。
接下来,我们将(ans)减去当前子树原先的贡献。
其次,将(Sum),当前节点的(Sum),以及当前节点的(Val/Calc)(如果为被修改节点则更新(Val),如果为被修改节点的祖先节点,则更新(Calc))各加上(val)。
下一步,我们判断(son)是否为当前节点的实儿子,如果是,则更新。注意更新时我们要将当前节点(Calc)先加上原右儿子的(Sum),然后减去(son)的(Sum)。
最后,我们将(ans)加上当前节点的新答案,然后继续处理其父节点即可。
代码
#include<bits/stdc++.h>
#define Type template<typename I>
#define N 400000
#define LL long long
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,ee,a[N+5],lnk[N+5];
struct edge
{
int to,nxt;
}e[(N<<1)+5];
class Class_FIO
{
private:
#define Fsize 100000
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,Fsize,stdin),A==B)?EOF:*A++)
#define pc(ch) (FoutSize^Fsize?Fout[FoutSize++]=ch:(fwrite(Fout,1,Fsize,stdout),Fout[(FoutSize=0)++]=ch))
#define Isi(x) (typeid(x).name()==typeid(1).name()||typeid(x).name()==typeid(1LL).name())
#define Isc(x) (typeid(x).name()==typeid('a').name())
int Top,FoutSize;char ch,*A,*B,Fin[Fsize],Fout[Fsize],Stack[Fsize];
public:
Class_FIO() {A=B=Fin;}
Type inline void read(I& x) {x=0;while(!isdigit(ch=tc()));while(x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));}
Type inline void write(I x)
{
if(Isi(x)) {while(Stack[++Top]=x%10+48,x/=10);while(Top) pc(Stack[Top--]);}
if(Isc(x)) pc(x);
}
template<typename I,typename... A> inline void read(I& x,A&... y) {read(x),read(y...);}
template<typename I,typename... A> inline void write(I x,A... y) {write(x),write(y...);}
inline void clear() {fwrite(Fout,1,FoutSize,stdout),FoutSize=0;}
}F;
class Class_LCT
{
private:
#define SIZE N
#define PushUp(x) (node[x].Sum=node[x].Val+node[x].Calc+node[node[x].Son[0]].Sum+node[node[x].Son[1]].Sum)
#define Rever(x) (swap(node[x].Son[0],node[x].Son[1]),node[x].Rev^=1)
#define PushDown(x) (node[x].Rev&&(Rever(node[x].Son[0]),Rever(node[x].Son[1]),node[x].Rev=0))
#define Which(x) (node[node[x].Father].Son[1]==x)
#define Connect(x,y,d) (node[node[x].Father=y].Son[d]=x)
#define IsRoot(x) (node[node[x].Father].Son[0]^x&&node[node[x].Father].Son[1]^x)
LL ans;int Stack[SIZE+5];
struct Tree
{
int Op,Father,Rev,Son[2];LL Val,Calc,Sum;
}node[SIZE+5];
inline void Rotate(int x)
{
register int fa=node[x].Father,pa=node[fa].Father,d=Which(x);
!IsRoot(fa)&&(node[pa].Son[Which(fa)]=x),node[x].Father=pa,Connect(node[x].Son[d^1],fa,d),Connect(fa,x,d^1),PushUp(fa),PushUp(x);
}
inline void Splay(int x)
{
register int fa=x,Top=0;
while(Stack[++Top]=fa,!IsRoot(fa)) fa=node[fa].Father;
while(Top) PushDown(Stack[Top]),--Top;
while(!IsRoot(x)) fa=node[x].Father,!IsRoot(fa)&&(Rotate(Which(x)^Which(fa)?x:fa),0),Rotate(x);
}
public:
inline void Init(int x)//初始化
{
register int i,v,Pos=x;register LL Max=node[x].Val=a[x];
for(i=lnk[x];i;i=e[i].nxt) (v=e[i].to)^node[x].Father&&(node[v].Father=x,Init(v),node[x].Calc+=node[v].Sum,Max<node[v].Sum&&(Max=node[Pos=v].Sum));
if(node[x].Sum=node[x].Calc+node[x].Val,(Max<<1)<=node[x].Sum) return (void)(ans+=node[x].Sum-1,node[x].Op=1);//若2*Max<=Sum,则为第一种情况,将ans加上Sum-1,并标记op为1,然后退出函数
ans+=node[x].Sum-Max<<1,x^Pos?(node[x].Calc-=node[node[x].Son[1]=Pos].Sum,node[x].Op=2):(node[x].Op=3);//否则,将ans加上2*(Sum-Max),若找到的不是这个节点本身,则为第二种情况,更新Calc,并标记op为2,否则标记op为3
}
inline void Update(int x,int son,int val)//修改
{
Splay(x);//将其旋转到根
register LL Sum=node[x].Sum-node[node[x].Son[0]].Sum;//记录除去祖先贡献后当前子树内的贡献
ans-=node[x].Op^1?Sum-(node[x].Op^3?node[node[x].Son[1]].Sum:node[x].Val)<<1:Sum-1,//减去原先的贡献
Sum+=val,node[x].Sum+=val,son?(node[x].Calc+=val):(node[x].Val+=val),//修改值
(node[son].Sum<<1)>Sum&&(node[x].Calc+=node[node[x].Son[1]].Sum,node[x].Calc-=node[node[x].Son[1]=son].Sum);
if((node[node[x].Son[1]].Sum<<1)>Sum) {ans+=Sum-node[node[x].Son[1]].Sum<<1,node[x].Op=2;goto loop;}
node[x].Son[1]&&(node[x].Calc+=node[node[x].Son[1]].Sum,node[x].Son[1]=0);
if((node[x].Val<<1)>Sum) {ans+=Sum-node[x].Val<<1,node[x].Op=3;goto loop;}
ans+=Sum-1,node[x].Op=1;
loop:node[x].Father&&(Update(node[x].Father,x,val),0);//继续处理祖先
}
inline LL GetAns() {return ans;}//返回答案
}LCT;
int main()
{
register int query_tot,i,x,y;
for(F.read(n,query_tot),i=1;i<=n;++i) F.read(a[i]);
for(i=1;i<n;++i) F.read(x,y),add(x,y),add(y,x);
LCT.Init(1),F.write(LCT.GetAns(),'
');
while(query_tot--) F.read(x,y),LCT.Update(x,0,y),F.write(LCT.GetAns(),'
');
return F.clear(),0;
}