【BZOJ3091】城市旅行(再次重拾LCT)
大致题意: 一棵树上每个点有一个点权,要求支持4种操作:删边;连边(若已连通则忽略);给一条路径上的权值加上某一个数;询问在一条路径上随机取两个点时,两点路径上权值和的期望是多少。
前言
为什么要说是再次重拾\(LCT\)呢?因为已经重拾过一次了。(可见这篇博客:【BZOJ2594】[WC2006] 水管局长数据加强版(重拾LCT),而且我相信还会有第三次、第四次,直至第n次重拾)
这是一道比较水的题(毕竟连我都能自己做出来),但基本涉及到了\(LCT\)的各项操作,所以作为一道练习\(LCT\)的题倒是不错的。
关于这道题,\(PushUp\)函数写完我一直觉得很虚,因此基本上一有错都开始调这个函数。
然而。。。最终发现似乎只有它没有出现\(bug\)?
尤其智障的是,一个\(Calc(x)=\sum_{i=1}^{x}i(x-i+1)\)的化简求值函数,我居然写错两次。。。
套路
首先,由于题目要求输出分数,因此我们不得不从求期望改为求各种情况下答案的总和。
那么,根据套路,我们分别计算每个数的贡献。然后发现,假设一个数是长度为\(len\)的路径上第\(i\)个数,那么它能被选中的情况数就是\(i(len-i+1)\)。
而看到删边、连边,自然想到\(LCT\)。因此,我们就要用\(Splay\)的每个节点存储其子树所代表的这条路径的答案。
则我们就要考虑两个问题:如何上传信息(\(PushUp\)),以及如何支持修改(\(PushDown\))。
\(PushUp\)
我们设\(P_x\)表示子树的答案,直接来推式子吧:
然后考虑\(P_{lc}=\sum_{i=1}^{Sz_{lc}}Vl_i\cdot i\cdot(Sz_{lc}-i+1),P_{rc}=\sum_{i=1}^{Sz_{rc}}Vr_i\cdot i\cdot(Sz_{rc}-i+1)\),代入得到:
由于\(V,P,Sz\)我们都可以看作一个常数,除去它们后剩下来的就是\(\sum_{i=1}^{Sz_{lc}}Vl_i\cdot i\)以及\(\sum_{i=1}^{Sz_{rc}}Vr_i(Sz_{rc}-i+1)\)。
(诶,写到这里才发现第二个式子中的\(Sz_{rc}\)若看作常量,那就与第一个式子相同,两者便可以一并维护了,但毕竟太麻烦了,还是分两个比较容易理解吧。)
则,我们设\(G_x=\sum_{i=1}^{Sz_x}V_x,LS_x=\sum_{i=1}^{Sz_x}V_x\cdot i,RS_x=\sum_{i=1}^{Sz_x}V_x\cdot(Sz_x-i+1)\),然后再考虑应该如何实现\(PushUp\),就有:
这个应该是比较好理解的,在此就不作赘述了。
\(PushDown\)
\(PushDown\)我们一共有两个标记:翻转标记(\(LCT\)自带),修改标记。
首先考虑翻转标记,这个比较简单,唯一要注意的是此时除了交换子节点之外,我们同时还要交换\(LS_x\)和\(RS_x\)。
然后考虑修改标记,假设为\(v\),则:
- 对于\(P_x\),由于所有值加上了\(v\),因此答案共加上了:
- 对于\(G_x\),显然是加上了\(v\times Sz_x\)。
- 对于\(LS_x\),就是加上了:
- 对于\(RS_x\),不难发现其实和\(LS_x\)是一样的。
于是我们就实现了维护。
至于其他的东西,就是\(LCT\)的普通模板了,具体实现可以详见代码。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 50000
#define LL long long
#define swap(x,y) (x^=y^=x^=y)
using namespace std;
int n,a[N+5];
class FastIO
{
private:
#define FS 100000
#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
#define pc(c) (C==E&&(clear(),0),*C++=c)
#define tn (x<<3)+(x<<1)
#define D isdigit(c=tc())
int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
Tp I Ty gcd(Con Ty& x,Con Ty& y) {return y?gcd(y,x%y):x;}
public:
I FastIO() {A=B=FI,C=FO,E=FO+FS;}
Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
Tp I void write(Ty x) {x<0&&(pc('-'),x=-x);W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
Tp I void writeF(Con Ty& x,Con Ty& y) {Ty g=gcd(x,y);write(x/g),pc('/'),writeln(y/g);}
I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
class LinkCutTree//LCT,其中有一半是define......
{
private:
#define S1(x) (1LL*(x)*((x)+1)/2)//一次方和
#define S2(x) (1LL*(x)*((x)+1)*(2*(x)+1)/6)//二次方和
#define Calc(x) (1LL*S1(x)*((x)+1)-S2(x))//计算所有点被统计次数的总和
#define IR(x) (O[O[x].F].S[0]^x&&O[O[x].F].S[1]^x)
#define Wh(x) (O[O[x].F].S[1]==x)
#define Co(x,y,d) (O[O[x].F=y].S[d]=x)
#define PU(x)\
(\
O[x].Sz=O[O[x].S[0]].Sz+O[O[x].S[1]].Sz+1,O[x].G=O[O[x].S[0]].G+O[O[x].S[1]].G+O[x].V,\
O[x].P=O[x].V*(O[O[x].S[0]].Sz+1)*(O[O[x].S[1]].Sz+1)+O[O[x].S[0]].P+O[O[x].S[1]].P,\
O[x].P+=O[O[x].S[0]].LS*(O[O[x].S[1]].Sz+1)+O[O[x].S[1]].RS*(O[O[x].S[0]].Sz+1),\
O[x].LS=O[O[x].S[0]].LS+O[O[x].S[1]].LS+(O[x].V+O[O[x].S[1]].G)*(O[O[x].S[0]].Sz+1),\
O[x].RS=O[O[x].S[1]].RS+O[O[x].S[0]].RS+(O[x].V+O[O[x].S[0]].G)*(O[O[x].S[1]].Sz+1)\
)//上传信息
#define PD(x) O[x].R&&(Re(O[x].S[0]),Re(O[x].S[1]),O[x].R=0),\
O[x].T&&(U(O[x].S[0],O[x].T),U(O[x].S[1],O[x].T),O[x].T=0)//下传标记
#define Re(x) (O[x].R^=1,swap(O[x].S[0],O[x].S[1]),swap(O[x].LS,O[x].RS))//翻转
#define U(x,v)\
(\
O[x].V+=v,O[x].T+=v,O[x].P+=Calc(O[x].Sz)*v,O[x].G+=O[x].Sz*v,\
O[x].LS+=S1(O[x].Sz)*v,O[x].RS+=S1(O[x].Sz)*v\
)//修改
#define MR(x) (Ac(x),S(x),Re(x))
#define Sp(x,y) (MR(x),Ac(y),S(y))
int St[N+5];struct node {int F,R,T,Sz,S[2];LL V,P,G,LS,RS;}O[N+5];
I void Ro(int x)
{
RI f=O[x].F,p=O[f].F,d=Wh(x);!IR(f)&&(O[p].S[Wh(f)]=x),
O[x].F=p,Co(O[x].S[d^1],f,d),Co(f,x,d^1),PU(f),PU(x);
}
I void S(int x)
{
RI f=x,T=0;W(St[++T]=f,!IR(f)) f=O[f].F;W(T) PD(St[T]),--T;
W(!IR(x)) f=O[x].F,!IR(f)&&(Ro(Wh(x)^Wh(f)?x:f),0),Ro(x);
}
I void Ac(RI x) {for(RI y=0;x;x=O[y=x].F) S(x),O[x].S[1]=y,PU(x);}
I int FR(RI x) {Ac(x),S(x);W(O[x].S[0]) PD(x),x=O[x].S[0];return S(x),x;}
public:
I void Init() {for(RI i=1;i<=n;++i) O[i].V=a[i],O[i].Sz=1,PU(i);}
I void Link(CI x,CI y) {MR(x),FR(y)^x&&(O[x].F=y);}
I void Cut(CI x,CI y) {MR(x),FR(y)==x&&O[y].F==x&&!O[y].S[0]&&(O[y].F=O[x].S[1]=0,PU(x));}
I bool Identify(CI x,CI y) {return MR(x),FR(y)==x;}//判断是否连通
I void Upt(CI x,CI y,CI v) {Sp(x,y),U(y,v);}//分裂出路径修改
I void Qry(CI x,CI y) {Sp(x,y),F.writeF(O[y].P,S1(O[y].Sz));}//分裂出路径询问
}LCT;
int main()
{
RI Qt,i,op,x,y,z;for(F.read(n),F.read(Qt),i=1;i<=n;++i) F.read(a[i]);LCT.Init();
for(i=1;i^n;++i) F.read(x),F.read(y),LCT.Link(x,y);
W(Qt--) switch(F.read(op),F.read(x),F.read(y),op)
{
case 1:LCT.Cut(x,y);break;case 2:LCT.Link(x,y);break;
case 3:F.read(z),LCT.Identify(x,y)&&(LCT.Upt(x,y,z),0);break;
case 4:LCT.Identify(x,y)?LCT.Qry(x,y):F.writeln(-1);break;
}return F.clear(),0;
}