一、题目

点此看题

二、解法

题目的提示已经足够明显了吧,肯定是要写一个 \(\tt link-cut-tree\) 。我们只需要求出总和,再除以方案数就是期望。然后可以算每个点为 \(\tt lca\) 时的贡献。

但是要对子树搞点事情,而子树分为虚子树和实子树(看连接的是什么边),所以两类都要维护,实子树可以用 \(\tt push\_up\) 操作维护,虚子树要在变动父子关系的时候维护,有点麻烦。要维护下列信息:

  • \(x\) 的虚子树大小(包括 \(x\) 这个点):\(siz\)
  • \(x\) 的子树中节点总个数:\(sum\)
  • \(x\) 节点包括其子树中的每个点的答案和:\(ans\)
  • \(x\) 子树内所有点的 \(siz\times a\) :\(all\)
  • \(x\) 所有虚子树的答案:\(ad\)
  • \(x\) 所有虚子树 \(siz\) 的平方求和:\(de\)

虚子树的信息你肯定是会维护的,\(sum\) 和 \(ans\) 你肯定也会,我就来详细讲一讲 \(ans\) 怎么算,要分成四部分:

  • 拿到其子树的答案:\(ans[ls]+ans[rs]+ad[x]\)
  • 虚子树之间的贡献,也就是在 \(siz[x]\) 中乱选两个点,再把两点选在同一个子树中的方案给删掉:\((siz[x]\times siz[x]-de[x])\times a[x]\)
  • 虚子树和实子树(\(x\) 的右儿子)之间的贡献,它们的 \(\tt lca\) 是 \(x\):\(2\times a[x]\times siz[x]\times sum[ch[x][1]]\)
  • \(x\) 的子树和 \(x\) 祖先的贡献(\(x\) 的左儿子),这个贡献由于 \(\tt splay\) 结构的原因没有被统计到,反正是求总和,我们把这个贡献放在 \(x\) 这里也没关系:\(2\times all[ch[x][0]]\times (sum[x]-sum[ch[x][0]])\)

然后因为要保证是有根树所以不能用 \(\tt makeroot\) 。那么 \(\tt link(x,y)\) 就把 \(x,y\) 都转到根然后连虚边,\(\tt cut\) 就把 \(x\) 转到根,\(y\) 转到 \(x\) 下面然后删虚边。在 \(\tt access,cut,link\) 的时候都要改虚子树的信息哦。

反正写的时候就是非常爽,非常爽。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 50005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,a[M],siz[M],sum[M],ans[M];char s[10];
int fa[M],all[M],ad[M],de[M],ch[M][2],par[M];double r;
/*
siz[x]表示x的虚子树大小(包括x)
sum[x]表示x为根的子树节点个数
ans[x]表示x的答案
all[x]表示x子树内的siz[y]*a[y]
ad[x]表示x虚子树的答案
de[x]表示x的虚子树的sum的平方的和
*/
void up(int x)
{
sum[x]=siz[x]+sum[ch[x][0]]+sum[ch[x][1]];
all[x]=all[ch[x][0]]+all[ch[x][1]]+a[x]*siz[x];
ans[x]=ans[ch[x][0]]+ans[ch[x][1]]+ad[x]//第一部分,直接累加
+a[x]*(siz[x]*siz[x]-de[x])//第二部分,虚子树的贡献
+2*a[x]*sum[ch[x][1]]*siz[x]//第三部分,虚实之间的贡献,lca是x
+2*all[ch[x][0]]*(sum[x]-sum[ch[x][0]]);//第四部分,把祖先的答案算到它上面
}
int nrt(int x)//判断是不是实边
{
return ch[par[x]][0]==x || ch[par[x]][1]==x;
}
int chk(int x)//判断是哪个儿子
{
return ch[par[x]][1]==x;
}
void rotate(int x)
{
int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
ch[y][k]=w;par[w]=y;
if(nrt(y)) ch[z][chk(y)]=x;par[x]=z;
ch[x][k^1]=y;par[y]=x;
up(y);up(x);
}
void splay(int x)//转到实链的最上面
{
while(nrt(x))
{
int y=par[x];
if(nrt(y))
{
if(chk(x)==chk(y)) rotate(y);
else rotate(x);
}
rotate(x);
}
}
void access(int x)
{
for(int y=0;x;x=par[y=x])
{
splay(x);
//先把ch[x][1]加进虚子树中
siz[x]+=sum[ch[x][1]];
ad[x]+=ans[ch[x][1]];
de[x]+=sum[ch[x][1]]*sum[ch[x][1]];
//再把y从虚子树中拿出来
siz[x]-=sum[y];
ad[x]-=ans[y];
de[x]-=sum[y]*sum[y];
ch[x][1]=y;up(x);
}
}
void link(int x,int y)//把(x,y)连一条边,x是祖先
{
access(y);
splay(y);
access(x);
splay(x);
par[y]=x;
siz[x]+=sum[y];
ad[x]+=ans[y];
de[x]+=sum[y]*sum[y];
up(x);
}
void cut(int x,int y)//把(x,y)这条边去掉,x是祖先
{
access(x);
splay(x);
splay(y);
par[y]=0;
siz[x]-=sum[y];
ad[x]-=ans[y];
de[x]-=sum[y]*sum[y];
up(x);
}
int check(int x,int y)//判断x是不是y的祖先
{
access(y);
splay(y);
splay(x);
return nrt(y);
}
signed main()
{
n=read();
for(int i=2;i<=n;i++)
fa[i]=read();
for(int i=1;i<=n;i++)
{
a[i]=ans[i]=all[i]=read();
siz[i]=sum[i]=1;
}
for(int i=2;i<=n;i++)
link(fa[i],i);
m=read();
access(1);
splay(1);
r=ans[1];
printf("%.10lf\n",r/n/n);
while(m--)
{
scanf("%s",s);
int x=read(),y=read();double r=0;
if(s[0]=='P')
{
if(check(x,y)) swap(x,y);
cut(fa[x],x);
fa[x]=y;
link(fa[x],x);
access(1);
splay(1);
r=ans[1];
}
else
{
access(x);
splay(x);
a[x]=y;
up(x);
r=ans[x];
}
printf("%.10lf\n",r/n/n);
}
}

CF482E ELCA的更多相关文章

  1. 【CF 482E】ELCA

    题意 题解 50pts 由于这题 \(2s\),所以可以信仰一波,暴力修改.查询. 暴力修改的复杂度是 \(O(n)\),暴力查询的复杂度是 \(O(n^2)\). 但不难发现可以通过记录子树大小来优 ...

  2. Codeforces 482E ELCA (LCT)

    题目链接 http://codeforces.com/contest/482/problem/E 题解 T2智商题T3大LCT题,我一个也不会= = CF的标算好像是分块?反正现在LCT都普及了就用L ...

  3. CF数据结构练习

    1. CF 438D The Child and Sequence 大意: n元素序列, m个操作: 1,询问区间和. 2,区间对m取模. 3,单点修改 维护最大值, 取模时暴力对所有>m的数取 ...

  4. CORBA简介

    使用.NET开发corba应用 一. 什么是IIOP.NET IIOP.NET 是通过使用基于corba的IIOP支持.NET.javaEE和corba组件实现无缝互操作的技术.如图1.1所示,这种解 ...

随机推荐

  1. cmder设置方法

    一.添加鼠标右键 Cmder.exe /REGISTER ALL 二.添加系统环境变量 我的电脑 > 右键属性 > 高级系统设置 > 环境变量 > 系统变量,在path中添加 ...

  2. Apple Watch Series 6 屏幕误触放大后无法还原问题和解决方案

    Apple Watch Series 6 屏幕误触放大后无法还原问题和解决方案 shit Apple,只能放大,不能缩小! 解决方案 关闭缩放功能 https://support.apple.com/ ...

  3. LeetCode 算法面试题汇总

    LeetCode 算法面试题汇总 算法面试题 https://leetcode-cn.com/problemset/algorithms/ https://leetcode-cn.com/proble ...

  4. js 大数计算

    js 大数计算 原理 JavaScript 安全整数 是 -253-1 ~ 253-1 ,即: -9007199254740991 ~ 9007199254740991; 换句话说,整数超过这个范围就 ...

  5. NGK的发行量是多少?NGK销毁机制是怎么样的?

    代币销毁(Coin Burning),是指将代币从流通中永久性去除.换句话说,被销毁的代币相当于被永久性冻结,再也无法流入市场.那为什么要进行代币销毁呢? 销毁加密货币,可以使剩余加密货币的价值升高, ...

  6. 翻译:《实用的Python编程》01_06_Files

    目录| 上一节(1.5 列表) | 下一节 (1.7 函数) 1.6 文件管理 大多数的程序需要从某处读取输入.本节讨论文件访问. 文件输入和输出 打开一个文件: f = open('foo.txt' ...

  7. 字符串拼接出现null的问题

    最近在开发的过程中遇到这样的问题,原因是在做一个需求的时候,要求将解密的号码和前缀进行拼接.一开始在这个拼接的过程中,没有考虑到数据校验的问题,因为有可能他的前缀或者其他需要拼接的字段在前端传递的过程 ...

  8. sql注入和union all关联查询的学习总结

    1.后台从页面取值进行sql查询时最好不要直接拼,如下代码: String sql = "SELECT wo.* " + " from push_command pu & ...

  9. TERSUS无代码开发(笔记02)-简单实例加法

    简单实例加法 1.用户端元件(显示元件)(40个) 图标 英文名称 元件名称 使用说明 服务器端 客户端 Pane 显示块 是一个显示块,是HTML的div标签   √ Row 行 行元件中的显示元件 ...

  10. Mysql训练:第二高的薪水(IFNULL,OFFSET,LIMIT)

    编写一个 SQL 查询,获取 Employee 表中第二高的薪水(Salary) . +----+--------+ | Id | Salary | +----+--------+ | 1 | 100 ...