把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【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_x=\sum_{i=1}^{Sz_{lc}}Vl_i\cdot i\cdot(Sz_{lc}+Sz_{rc}+1-i+1)+V_x\cdot(Sz_{lc}+1)\cdot(Sz_{rc}+1)+\sum_{i=1}^{Sz_{rc}}Vr_i\cdot(Sz_{lc}+1+i)\cdot(Sz_{rc}-i+1) \]

然后考虑\(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)\),代入得到:

\[P_x=V_x\cdot(Sz_{lc}+1)\cdot(Sz_{rc}+1)+P_{lc}+P_{rc}+\sum_{i=1}^{Sz_{lc}}Vl_i\cdot i\cdot(Sz_{rc}+1)+\sum_{i=1}^{Sz_{rc}}Vr_i\cdot (Sz_{lc}+1)\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\),就有:

\[P_x=V_x\cdot(Sz_{lc}+1)\cdot(Sz_{rc}+1)+P_{lc}+P_{rc}+LS_{lc}\cdot(Sz_{rc}+1)+RS_{rc}\cdot (Sz_{lc}+1) \]

\[G_x=G_{lc}+V_x+G_{rc} \]

\[LS_x=LS_{lc}+LS_{rc}+(V_x+G_{rc})(Sz_{lc}+1) \]

\[RS_x=RS_{rc}+RS_{lc}+(V_x+G_{lc})(Sz_{rc}+1) \]

这个应该是比较好理解的,在此就不作赘述了。

\(PushDown\)

\(PushDown\)我们一共有两个标记:翻转标记(\(LCT\)自带),修改标记。

首先考虑翻转标记,这个比较简单,唯一要注意的是此时除了交换子节点之外,我们同时还要交换\(LS_x\)\(RS_x\)

然后考虑修改标记,假设为\(v\),则:

  • 对于\(P_x\),由于所有值加上了\(v\),因此答案共加上了:

\[v\sum_{i=1}^{Sz_x}i(Sz_x-i+1)=v(\frac{Sz_x\times(Sz_x+1)}2\times(Sz_x+1)-\frac{Sz_x(Sz_x+1)(2Sz_x+1)}6) \]

  • 对于\(G_x\),显然是加上了\(v\times Sz_x\)
  • 对于\(LS_x\),就是加上了:

\[v\sum_{i=1}^{Sz_x}i=v\frac{Sz_x(Sz_x+1)}2 \]

  • 对于\(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;
}
posted @ 2020-05-14 10:07  TheLostWeak  阅读(131)  评论(0编辑  收藏  举报