bzoj3091

时间:2023-03-09 16:11:25
bzoj3091

最近屯题都忘了把解题报告写上了
这道题是一道比较烦的LCT,我们先考虑每个点上到底要维护什么
我们设路径上有n个点,从起点到终点编号为1~n
显然期望=S/[(n+1)n div 2]
S=∑a[i]*i*(n-i+1) //这里理解一下
=∑a[i]*[i*(n+1)-i*i]=(n+1)*∑a[i]*i-∑a[i]*i*i;
考虑到找出x,y之间的路径就是把x,y路径上的点弄到一棵Auxiliary tree上
对于每个点x,我们显然要维护以x为根的子树对应的序列的∑a[i]*i和∑a[i]*i*i,记为s2[x],s3[x]
对于上面表达式的i,就是每个点在树(子树)上的名次
下面的关键就是我们如何用其左右孩子的信息来维护父亲x,设l为x左孩子,r为x右孩子
这里主要的影响在于对右孩子,原来右孩子的维护的是编号为1~size[r]的序列的s2[],s3[]
现在我们加上去的是编号为size[l]+2~size[x]序列的s2[],s3[]
我们考虑会会增加多少,对于s2 ∑a[i]*(i+p)-∑a[i]*i=p*∑a[i]
对于s3 有∑a[i]*(i+p)^2-∑a[i]*i^2=∑a[i]*(2*p*i+p*p)=p*p*∑a[i]+2*p*∑a[i]*i
这里维护就很显然了,我们还要再维护一个s1[]记录∑a[i]
而修改相对比较简单,其实就是对s1,s2,s3分别加上d*n,d*∑i,d*∑i*i 这个直接上公式
但这里还并没有完,我们用的LCT有个操作叫换根,而换根要翻转左右子树
而这样左右子树的编号就反过来了,从而节点上记录s2[],s3[]就不正确了
我的解决方法是再维护ss2[],ss3[],记录倒序编号时∑a[i]*i和∑a[i]*i*i
然后要左右子树交换时,就交换ss2[],s2[]和ss3[]和s3[]
具体实现细节见程序

 type node=record
po,next:longint;
end; var son:array[..,..] of longint;
p,a,q,fa,add,size:array[..] of longint;
s1,ss2,ss3,s2,s3:array[..] of int64;
rev:array[..] of boolean;
e:array[..] of node;
cc,t,len,ch,n,m,x,y,z,i:longint; function gcd(a,b:int64):int64;
begin
if b= then exit(a)
else exit(gcd(b,a mod b));
end; procedure build(x,y:longint);
begin
inc(len);
e[len].po:=y;
e[len].next:=p[x];
p[x]:=len;
end; procedure dfs(x:longint);
var i,y:longint;
begin
i:=p[x];
while i<> do
begin
y:=e[i].po;
if fa[x]<>y then
begin
fa[y]:=x;
dfs(y);
end;
i:=e[i].next;
end;
end; procedure swap(var a,b:int64);
var c:int64;
begin
c:=a;
a:=b;
b:=c;
end; procedure reverse(x:longint); //如上述
var p:longint;
begin
swap(ss2[x],s2[x]);
swap(ss3[x],s3[x]);
rev[x]:=not rev[x];
end; function root(x:longint):boolean;
begin
exit((son[fa[x],]<>x) and (son[fa[x],]<>x));
end; procedure calc(x,z:longint);
var p,c,d:int64;
begin
inc(add[x],z);
inc(a[x],z);
p:=size[x];
c:=int64(z)*(p+)*p div ;
d:=int64(z)*(p+)*p*(*p+) div ; //平方和公式
s1[x]:=s1[x]+int64(z)*p;
s2[x]:=s2[x]+c;
s3[x]:=s3[x]+d;
ss2[x]:=ss2[x]+c;
ss3[x]:=ss3[x]+d;
end; procedure update(x:longint);
var l,r:longint;
p,q:int64;
begin
l:=son[x,];
r:=son[x,];
size[x]:=size[l]+size[r]+;
p:=size[l]+; //正序编号
q:=size[r]+; //反序标号
s1[x]:=s1[l]+s1[r]+a[x];
s2[x]:=s2[l]+int64(a[x])*p+s2[r]+s1[r]*p;
s3[x]:=s3[l]+int64(a[x])*p*p+s3[r]+p*p*s1[r]+*p*s2[r];
ss2[x]:=ss2[r]+int64(a[x])*q+ss2[l]+s1[l]*q;
ss3[x]:=ss3[r]+int64(a[x])*q*q+ss3[l]+q*q*s1[l]+*q*ss2[l];
end; procedure push(x:longint);
var r:longint;
begin
if rev[x] then
begin
r:=son[x,]; son[x,]:=son[x,]; son[x,]:=r;
if son[x,]<> then reverse(son[x,]);
if son[x,]<> then reverse(son[x,]);
rev[x]:=false;
end;
if add[x]<> then
begin
if son[x,]<> then calc(son[x,],add[x]);
if son[x,]<> then calc(son[x,],add[x]);
add[x]:=;
end;
end; procedure rotate(x,w:longint);
var y:longint;
begin
y:=fa[x];
if not root(y) then
begin
if son[fa[y],]=y then son[fa[y],]:=x
else son[fa[y],]:=x;
end;
fa[x]:=fa[y];
son[y,-w]:=son[x,w];
fa[son[x,w]]:=y;
son[x,w]:=y;
fa[y]:=x;
update(y);
end; procedure splay(x:longint);
var i,y:longint;
fl:boolean;
begin
t:=;
i:=x;
while not root(i) do
begin
inc(t);
q[t]:=i;
i:=fa[i];
end;
inc(t);
q[t]:=i;
for i:=t downto do
push(q[i]);
if t= then exit;
fl:=true;
while fl do
begin
y:=fa[x];
if y=q[t] then
begin
if son[y,]=x then rotate(x,)
else rotate(x,);
fl:=false;
end
else begin
if fa[y]=q[t] then fl:=false;
if son[fa[y],]=y then
begin
if son[y,]=x then rotate(y,)
else rotate(x,);
rotate(x,);
end
else begin
if son[y,]=x then rotate(x,)
else rotate(y,);
rotate(x,);
end;
end;
end;
update(x);
end; procedure access(x:longint);
var y:longint;
begin
y:=;
repeat
splay(x);
son[x,]:=y;
update(x);
y:=x;
x:=fa[x];
until x=;
end; function find(x,y:longint):boolean;
begin
while son[y,]<> do y:=son[y,]; //Auxiliary tree最左的点即为所在树的根节点
if y=x then exit(true) else exit(false);
end; procedure makeroot(x:longint);
begin
access(x);
splay(x);
reverse(x); //注意,在这WA了几次
end; procedure path(x,y:longint);
begin
makeroot(x);
access(y);
splay(y);
end; procedure link(x,y:longint);
begin
if cc= then exit; //一点小优化,cc记录删掉的边数
path(x,y);
if not find(x,y) then
begin
fa[x]:=y;
dec(cc);
end;
end; procedure cut(x,y:longint);
begin
path(x,y);
if (son[y,]=x) and (son[x,]=) then //注意两点间有边的判定
begin
inc(cc);
fa[x]:=;
son[y,]:=;
end;
end; procedure change(x,y,z:longint);
begin
path(x,y);
if (cc=) or find(x,y) then calc(y,z);
end; procedure ask(x,y:longint);
var a,b,c:int64;
begin
path(x,y);
if (cc=) or find(x,y) then
begin
c:=size[y];
a:=(c+)*s2[y]-s3[y];
b:=c*(c+) div ;
c:=gcd(a,b);
writeln(a div c,'/',b div c);
end
else writeln(-);
end; begin
readln(n,m);
for i:= to n do
begin
read(a[i]);
size[i]:=;
end;
for i:= to n- do
begin
readln(x,y);
build(x,y);
build(y,x);
end;
dfs(); //这里先dfs构造树,如果直接link的话会TLE目测
for i:= to m do
begin
read(ch,x,y);
if ch= then
cut(x,y)
else if ch= then
link(x,y)
else if ch= then
ask(x,y)
else begin
read(z);
change(x,y,z);
end;
readln;
end;
end.