题意

  

  

  

题解

50pts

  由于这题 \(2s\),所以可以信仰一波,暴力修改、查询。

  暴力修改的复杂度是 \(O(n)\),暴力查询的复杂度是 \(O(n^2)\)。

  但不难发现可以通过记录子树大小来优化查询。具体地就是我们发现可以从每个点出发走到根,每经过一个点就计算一下起点与多少个点的 \(\text{LCA}\) 是这个点。预处理一下以每个点为根的子树大小即可。

  优化一下,我们发现直接 \(\text{dfs}\) 一遍整颗树就能统计所有答案。对于每个点 \(u\) 枚举一条儿子边,设该儿子边指向的儿子为 \(v\),然后令第一只虫子在以 \(v\) 为根的子树内,第二只虫子在以 \(u\) 为根的子树内且不在以 \(v\) 为根的子树内,此时有 \(size[v]\times (size[u]-size[v])\) 对点的 \(\text{LCA}\) 为 \(u\),所以答案要累加 \(size[v]\times (size[u]-size[v])\times a[u]\)。这样我们就漏掉了第一支虫子在 \(u\) 上的情况,最后再把答案加 \(size[u]\times a[u]\) 即可。

  于是查询的复杂度优化到了 \(O(n)\),总复杂度 \(O(n^2)\)。

  然而我 \(O(n^3)\) 的暴力过了 \(\text{50pts}\)……下面是我的 \(\text{code}\)

#include<bits/stdc++.h>
#define ll long long
#define N 100010
using namespace std;
inline int read(){
int x=0; bool f=1; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
if(f) return x;
return 0-x;
} int n,m,a[N];
struct edge{int v,nxt; bool fx,exist;}e[N<<3];
int hd[N],cnt;
inline void add(int u, int v, bool fx){e[cnt]=(edge){v,hd[u],fx,1}, hd[u]=cnt++;}
int fa[N],siz[N]; double ans;
void getSiz(int u){
siz[u]=1;
for(int i=hd[u]; ~i; i=e[i].nxt) if(e[i].exist && !e[i].fx) getSiz(e[i].v), siz[u]+=siz[e[i].v];
//cout<<u<<' '<<siz[u]<<endl;
}
void dfs(int u, int f){
//cout<<a[u]<<' '<<siz[u]-siz[fa]<<endl;
ans += 1ll * a[u] * (siz[u]-siz[f]);
if(fa[u]) dfs(fa[u],u);
}
double getAns(){
ans=0;
for(int i=1; i<=n; ++i) dfs(i,0);
//cout<<ans<<endl;
return ans/=(ll)n*n;
}
int main(){
n=read();
int u,v;
memset(hd,-1,sizeof hd);
for(int i=2; i<=n; ++i) fa[i]=read(), add(fa[i],i,0), add(i,fa[i],1);
for(int i=1; i<=n; ++i) a[i]=read();
getSiz(1);
printf("%.10lf\n",getAns());
m=read();
int opt;
for(int i=1; i<=m; ++i){
opt=read(), u=read(), v=read();
if(opt==1){
int x=v; bool flag=0;
while(x){
x=fa[x];
if(x==u){flag=1; break;}
}
if(flag) swap(u,v); x=fa[u];
while(x){
siz[x]-=siz[u];
x=fa[x];
}
for(int j=hd[fa[u]]; ~j; j=e[j].nxt) if(e[j].exist && !e[j].fx && e[j].v==u){e[j].exist=e[j^1].exist=0; break;} fa[u]=v, add(v,u,0), add(u,v,1);
//getSiz(1);
x=v;
while(x){
siz[x]+=siz[u];
x=fa[x];
}
}
else a[u]=v;
printf("%.10lf\n",getAns());
}
return 0;
}

70pts(CF 原题标算)

  分块。

100pts

  显然 LCT。

  回头考虑 \(\text{50pts}\) 做法,它的式子可以简化!

  一个点对答案的贡献是

  \(\{\sum\limits_v size[v]\times (size[u]-size[v])+size[u]\}\times a[u]\)(\(v\) 是 \(u\) 的儿子)

  \(=\{(size[u]\times \sum\limits_v size[v]) - \sum\limits_v size[v]^2 + size[u]\}\times a[u]\)

  \(=\{size[u]\times (size[u]-1) - \sum\limits_v size[v]^2 + size[u]\}\times a[u]\)

  \(=\{size[u]^2 - \sum\limits_v size[v]^2\}\times a[u]\)

  那我们是不是可以在 LCT 的每个点上维护这个答案?

  下面考虑一下需要维护哪些信息

  首先 LCT 上每个点肯定要维护该点及所有虚子树的 \(size\) 和,设其为 \(siz\)。

  然后我们需要维护该点及所有实子树的 \(size\) 和,设其为 \(siz_sum\)。

  注意:大家都知道虚子树包括所有实边连接的后代,但可能在实子树是否包含虚边连接后代这个问题有歧义。这里规定,实子树也包括所有虚边连接的后代!比如这张图(实线表示实边,虚线表示虚边)

  

  这 \(6\) 个点是一棵实子树,而不是只有上面 \(3\) 个点组成一棵实子树!

  在下文中,如果要指上面 \(3\) 个点组成的子树,我们称其为 在 splay 上的某个子树

  然后我们需要记录虚子树大小平方和,用于计算该点对答案的贡献,设其为 \(siz\_sqr\)。

  然后我们需要统计整棵树的答案,所以需要把子树的答案往父亲合并,于是记录虚子树 \(ans\) 和,设其为 \(ans\_sum\)。

  用 \(siz\_sum\) 和 \(ans\_sum\) 即可算出 splay 中以这个点为根的子树中所有点对答案的贡献和,记其为 \(ans\)。

  然后我们画个图,尝试合并一下

  

  图中粗线为重链,细线为轻链,蓝色点表示维护重链的 splay 的根(为了理解方便就直接设成了根,其实设成 splay 里的任意一个非叶子节点都行)。

  合并的时候,发现若一只虫子在蓝色节点的右实子树中(即橙色圈圈区域),另一只虫子在蓝色节点的左实子树中(即红色圈圈区域),那这两点的 \(\text{LCA}\) 并不是蓝色节点,而是其在 splay 上的左子树中的点。不难发现,这种情况下左子树上的某个点为两点的 \(\text{LCA}\),当且仅当有一个点在该点的虚子树中。所以我们再给每个点记录一个变量 \(all\),表示 splay 中以它为根的子树中,所有点的 \(siz\times a[k]\) 之和(设点编号为 \(k\))。

  \(\text{pushup}\) 时,一个点的 \(ans\) 需要考虑较多因素,即更新成以下几项的和:

  • 两个实子树和所有虚子树的 \(ans\)
  • 两只虫子分别在两个不同的虚子树中
  • 一只虫子在任一虚子树中,另一只虫子在右实子树中。这时交换两只虫子后的方案没被统计,所以方案数 \(\times\space 2\)
  • 一只虫子在任一虚子树或右实子树中,另一只虫子在左实子树中。这时交换两只虫子后的方案没被统计,所以方案数 \(\times\space 2\)

  显然,后三条都是计算该点对答案的贡献,即有多少对点以这个点为 \(\text{LCA}\)。

  然后就可以写成如下代码的 \(\text{pushup}\) 了。

  

  注意一个细节:一般我们写 LCT 维护的都是无根树,但这题 LCT 护的是有根树,所以 \(\text{makeroot}\) 函数不要翻转子树(这会修改树根)。

  取答案的话,直接取 LCT 最顶部节点的 \(ans\) 即可。如果你不知道顶部节点的编号,随便把一个节点 \(\text{makeroot}\) 一下旋转到顶部就行了。

#include<bits/stdc++.h>
#define ll long long
#define N 100010
using namespace std;
inline int read(){
int x=0; bool f=1; char c=getchar();
for(;!isdigit(c); c=getchar()) if(c=='-') f=0;
for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
if(f) return x;
return 0-x;
}
int n,m,a[N];
namespace LCT{
#define son(x,k) tr[x].son[k]
struct Tree{
int son[2],fa;
ll siz,siz_sum,siz_sqr,ans_sum,all,ans;
}tr[N]; inline bool isRoot(int x){
return son(tr[x].fa,0)!=x && son(tr[x].fa,1)!=x;
}
inline bool idf(int x){
return son(tr[x].fa,1)==x;
}
void pushup(int x){
tr[x].siz_sum = tr[son(x,0)].siz_sum + tr[son(x,1)].siz_sum + tr[x].siz;
tr[x].ans = tr[son(x,0)].ans + tr[son(x,1)].ans + tr[x].ans_sum
+ (1ll * tr[x].siz * tr[x].siz - tr[x].siz_sqr
+ 2ll * tr[x].siz * tr[son(x,1)].siz_sum) * a[x]
+ 2ll * tr[son(x,0)].all * (tr[x].siz_sum-tr[son(x,0)].siz_sum);
tr[x].all = tr[son(x,0)].all + tr[son(x,1)].all + a[x] * tr[x].siz;
//cout<<tr[x].siz_sum<<' '<<tr[x].ans<<' '<<tr[x].all<<endl;
}
void vir_pushup(int x, int y, int v){
tr[x].siz += v * tr[y].siz_sum;
tr[x].siz_sqr += v * tr[y].siz_sum * tr[y].siz_sum;
tr[x].ans_sum += v * tr[y].ans;
}
inline void connect(int x, int f, int fx){
tr[x].fa=f, son(f,fx)=x;
}
inline void rotate(int x){
int y=tr[x].fa, z=tr[y].fa, idf_x=idf(x), idf_y=idf(y), B=tr[x].son[idf_x^1];
if(!isRoot(y)) connect(x,z,idf_y);
else tr[x].fa=z;
connect(B,y,idf_x), connect(y,x,idf_x^1);
pushup(y), pushup(x);
}
void splay(int x){
for(; !isRoot(x); ){
int f=tr[x].fa;
//cout<<"splay:"<<x<<' '<<f<<endl;
if(!isRoot(f)) rotate(idf(x)==idf(f) ? f : x);
rotate(x);
}
}
void access(int x){
for(int y=0; x; x=tr[y=x].fa){
//cout<<x<<' '<<y<<endl;
splay(x);
vir_pushup(x,son(x,1),1);
son(x,1)=y;
vir_pushup(x,son(x,1),-1);
pushup(x);
//cout<<x<<' '<<y<<endl;
}
}
inline void makeroot(int x){
access(x), splay(x);
}
void link(int x, int y){
//cout<<y<<endl;
makeroot(y), makeroot(x);
//cout<<y<<endl;
tr[y].fa=x;
vir_pushup(x,y,1), pushup(x);
}
void cut(int x, int y){
makeroot(x), splay(y);
tr[y].fa=0;
vir_pushup(x,y,-1), pushup(x);
}
bool isAnc(int x, int y){ //判断x是不是y的祖先,是则返回1
makeroot(y), splay(x);
if(isRoot(y)) return 0;
return 1;
}
}using namespace LCT;
int fa[N];
int main(){
n=read();
for(int i=2; i<=n; ++i) fa[i]=read();
for(int i=1; i<=n; ++i){
a[i]=read();
tr[i].all = tr[i].ans = a[i];
tr[i].siz_sum = tr[i].siz = 1;
}
for(int i=2; i<=n; ++i) link(fa[i],i);
access(1), splay(1);
printf("%.10lf\n",(double)tr[1].ans/n/n);
m=read();
while(m--){
int opt=read(), x=read(), y=read();
if(opt==1){
if(!isAnc(x,y)) cut(fa[x],x), link(y,x), fa[x]=y;
else cut(fa[y],y), link(x,y),fa[y]=x;
access(1), splay(1);
printf("%.10lf\n",(double)tr[1].ans/n/n);
}
else{
makeroot(x), a[x]=y, pushup(x);
printf("%.10lf\n",(double)tr[x].ans/n/n);
}
}
return 0;
}

  过两天复习一波 LCT,这数据结构真是个害人不浅的东西

【CF 482E】ELCA的更多相关文章

  1. 【CF#338D】GCD Table

    [题目描述] 有一张N,M<=10^12的表格,i行j列的元素是gcd(i,j) 读入一个长度不超过10^4,元素不超过10^12的序列a[1..k],问是否在某一行中出现过 [题解] 要保证g ...

  2. 【CF#303D】Rotatable Number

    [题目描述] Bike是一位机智的少年,非常喜欢数学.他受到142857的启发,发明了一种叫做“循环数”的数. 如你所见,142857是一个神奇的数字,因为它的所有循环排列能由它乘以1,2,...,6 ...

  3. 【CF 463F】Escape Through Leaf

    题意 给你一棵 \(n\) 个点的树,每个节点有两个权值 \(a_i,b_i\). 从一个点 \(u\) 可以跳到以其为根的子树内的任意一点 \(v\)(不能跳到 \(u\) 自己),代价是 \(a_ ...

  4. 【CF 453A】 A. Little Pony and Expected Maximum(期望、快速幂)

    A. Little Pony and Expected Maximum time limit per test 1 second memory limit per test 256 megabytes ...

  5. 【CF 585E】 E. Present for Vitalik the Philatelist

    E. Present for Vitalik the Philatelist time limit per test 5 seconds memory limit per test 256 megab ...

  6. 【35.20%】【CF 706D】Vasiliy's Multiset

    time limit per test 4 seconds memory limit per test 256 megabytes input standard input output standa ...

  7. 【26.8%】【CF 46D】Parking Lot

    time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...

  8. 【31.42%】【CF 714A】Meeting of Old Friends

    time limit per test 1 second memory limit per test 256 megabytes input standard input output standar ...

  9. 【31.95%】【CF 714B】Filya and Homework

    time limit per test 1 second memory limit per test 256 megabytes input standard input output standar ...

随机推荐

  1. Python使用filetype精确判断文件类型

    Python使用filetype精确判断文件类型 判断文件类型在开发中非常常见的需求,怎样才能准确的判断文件类型呢?首先大家想到的是文件的后缀,但是非常遗憾的是这种方法是非常不靠谱的,因为文件的后缀是 ...

  2. mac 安装photoshop + 破解

    项目开发中毫无疑问会用到图片,一般情况都是UI将图片切好的,只是,有时候项目中少了一张图片或者是改变图片的尺寸之类的问题,这里我们就不需要每次都找UI要图片了,作为程序员这些基础工具的使用,咱们还是要 ...

  3. 【D3D12学习手记】4.1.6 Resources and Descriptors

    在渲染过程中,GPU将写资源(resources)(例如,后缓冲区,深度/模板缓冲区),读资源(例如,描述表面外观的纹理,存储场景中几何体3D位置的缓冲区).在我们发出绘图命令之前,我们需要将资源绑定 ...

  4. 网络实验 03-交换机划分VLAN配置

    交换机划分VLAN配置 一.实验目标 理解虚拟 LAN(VLAN)基本原理 掌握一般交换机按端口划分 VLAN的配置方法 掌握Tag VLAN配置方法 二.实验背景 某一公司内财务部.销售部的PC通过 ...

  5. PJzhang:360压缩的用户许可协议和隐私政策阅读

    猫宁!!! 参考链接:http://yasuo.360.cn/ 当我们安装一个软件,不管是手机上的还是电脑上的,很少会去看它们的用户许可协议和隐私政策,而有的时候软件甚至都不提醒我们还有用户许可协议和 ...

  6. python笔记之python基础

    python基础语法 1.变量命名规则 1).变量必须以字母或者下划线_开头 2).变量可由字母.数字.下划线_组成 3).变量对大小写敏感 2.多行语句 当编写的代码较长,需要换行,可使用 \ 进行 ...

  7. LINQ查询表达式详解(1)——基本语法、使用扩展方法和Lambda表达式简化LINQ查询

    简介 使用线程的主要原因:应用程序中一些操作需要消耗一定的时间,比如对文件.数据库.网络的访问等等,而我们不希望用户一直等待到操作结束,而是在此同时可以进行一些其他的操作.  这就可以使用线程来实现. ...

  8. GS7 安装使用Oracle19c 客户端的说明

    1. 最近Oracle放出了 windows版本的oracle19c的安装文件(具体时间不详, 自己知道的时候比较晚了) 2. 发现文件其实比较多如图示: 3. 经过自己测试实现发现 不能使用  如下 ...

  9. 云数据库RDS MySQL 版

    阿里云关系型数据库(Relational Database Service,简称RDS)是一种稳定可靠.可弹性伸缩的在线数据库服务.基于阿里云分布式文件系统和SSD盘高性能存储,RDS支持MySQL. ...

  10. C++多线程基础学习笔记(四)

    一.创建多个子线程 前面三章讲的例子都是只有一个子线程和主线程,然而实际中有多个子线程.那么下面介绍如何创建多个子线程. #include <iostream> #include < ...