题意

  

  

  

题解

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}\)

  1. #include<bits/stdc++.h>
  2. #define ll long long
  3. #define N 100010
  4. using namespace std;
  5. inline int read(){
  6. int x=0; bool f=1; char c=getchar();
  7. for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
  8. for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
  9. if(f) return x;
  10. return 0-x;
  11. }
  12. int n,m,a[N];
  13. struct edge{int v,nxt; bool fx,exist;}e[N<<3];
  14. int hd[N],cnt;
  15. inline void add(int u, int v, bool fx){e[cnt]=(edge){v,hd[u],fx,1}, hd[u]=cnt++;}
  16. int fa[N],siz[N];
  17. double ans;
  18. void getSiz(int u){
  19. siz[u]=1;
  20. 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];
  21. //cout<<u<<' '<<siz[u]<<endl;
  22. }
  23. void dfs(int u, int f){
  24. //cout<<a[u]<<' '<<siz[u]-siz[fa]<<endl;
  25. ans += 1ll * a[u] * (siz[u]-siz[f]);
  26. if(fa[u]) dfs(fa[u],u);
  27. }
  28. double getAns(){
  29. ans=0;
  30. for(int i=1; i<=n; ++i) dfs(i,0);
  31. //cout<<ans<<endl;
  32. return ans/=(ll)n*n;
  33. }
  34. int main(){
  35. n=read();
  36. int u,v;
  37. memset(hd,-1,sizeof hd);
  38. for(int i=2; i<=n; ++i) fa[i]=read(), add(fa[i],i,0), add(i,fa[i],1);
  39. for(int i=1; i<=n; ++i) a[i]=read();
  40. getSiz(1);
  41. printf("%.10lf\n",getAns());
  42. m=read();
  43. int opt;
  44. for(int i=1; i<=m; ++i){
  45. opt=read(), u=read(), v=read();
  46. if(opt==1){
  47. int x=v; bool flag=0;
  48. while(x){
  49. x=fa[x];
  50. if(x==u){flag=1; break;}
  51. }
  52. if(flag) swap(u,v);
  53. x=fa[u];
  54. while(x){
  55. siz[x]-=siz[u];
  56. x=fa[x];
  57. }
  58. 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;}
  59. fa[u]=v, add(v,u,0), add(u,v,1);
  60. //getSiz(1);
  61. x=v;
  62. while(x){
  63. siz[x]+=siz[u];
  64. x=fa[x];
  65. }
  66. }
  67. else a[u]=v;
  68. printf("%.10lf\n",getAns());
  69. }
  70. return 0;
  71. }

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}\) 一下旋转到顶部就行了。

  1. #include<bits/stdc++.h>
  2. #define ll long long
  3. #define N 100010
  4. using namespace std;
  5. inline int read(){
  6. int x=0; bool f=1; char c=getchar();
  7. for(;!isdigit(c); c=getchar()) if(c=='-') f=0;
  8. for(; isdigit(c); c=getchar()) x=(x<<3)+(x<<1)+(c^'0');
  9. if(f) return x;
  10. return 0-x;
  11. }
  12. int n,m,a[N];
  13. namespace LCT{
  14. #define son(x,k) tr[x].son[k]
  15. struct Tree{
  16. int son[2],fa;
  17. ll siz,siz_sum,siz_sqr,ans_sum,all,ans;
  18. }tr[N];
  19. inline bool isRoot(int x){
  20. return son(tr[x].fa,0)!=x && son(tr[x].fa,1)!=x;
  21. }
  22. inline bool idf(int x){
  23. return son(tr[x].fa,1)==x;
  24. }
  25. void pushup(int x){
  26. tr[x].siz_sum = tr[son(x,0)].siz_sum + tr[son(x,1)].siz_sum + tr[x].siz;
  27. tr[x].ans = tr[son(x,0)].ans + tr[son(x,1)].ans + tr[x].ans_sum
  28. + (1ll * tr[x].siz * tr[x].siz - tr[x].siz_sqr
  29. + 2ll * tr[x].siz * tr[son(x,1)].siz_sum) * a[x]
  30. + 2ll * tr[son(x,0)].all * (tr[x].siz_sum-tr[son(x,0)].siz_sum);
  31. tr[x].all = tr[son(x,0)].all + tr[son(x,1)].all + a[x] * tr[x].siz;
  32. //cout<<tr[x].siz_sum<<' '<<tr[x].ans<<' '<<tr[x].all<<endl;
  33. }
  34. void vir_pushup(int x, int y, int v){
  35. tr[x].siz += v * tr[y].siz_sum;
  36. tr[x].siz_sqr += v * tr[y].siz_sum * tr[y].siz_sum;
  37. tr[x].ans_sum += v * tr[y].ans;
  38. }
  39. inline void connect(int x, int f, int fx){
  40. tr[x].fa=f, son(f,fx)=x;
  41. }
  42. inline void rotate(int x){
  43. int y=tr[x].fa, z=tr[y].fa, idf_x=idf(x), idf_y=idf(y), B=tr[x].son[idf_x^1];
  44. if(!isRoot(y)) connect(x,z,idf_y);
  45. else tr[x].fa=z;
  46. connect(B,y,idf_x), connect(y,x,idf_x^1);
  47. pushup(y), pushup(x);
  48. }
  49. void splay(int x){
  50. for(; !isRoot(x); ){
  51. int f=tr[x].fa;
  52. //cout<<"splay:"<<x<<' '<<f<<endl;
  53. if(!isRoot(f)) rotate(idf(x)==idf(f) ? f : x);
  54. rotate(x);
  55. }
  56. }
  57. void access(int x){
  58. for(int y=0; x; x=tr[y=x].fa){
  59. //cout<<x<<' '<<y<<endl;
  60. splay(x);
  61. vir_pushup(x,son(x,1),1);
  62. son(x,1)=y;
  63. vir_pushup(x,son(x,1),-1);
  64. pushup(x);
  65. //cout<<x<<' '<<y<<endl;
  66. }
  67. }
  68. inline void makeroot(int x){
  69. access(x), splay(x);
  70. }
  71. void link(int x, int y){
  72. //cout<<y<<endl;
  73. makeroot(y), makeroot(x);
  74. //cout<<y<<endl;
  75. tr[y].fa=x;
  76. vir_pushup(x,y,1), pushup(x);
  77. }
  78. void cut(int x, int y){
  79. makeroot(x), splay(y);
  80. tr[y].fa=0;
  81. vir_pushup(x,y,-1), pushup(x);
  82. }
  83. bool isAnc(int x, int y){ //判断x是不是y的祖先,是则返回1
  84. makeroot(y), splay(x);
  85. if(isRoot(y)) return 0;
  86. return 1;
  87. }
  88. }using namespace LCT;
  89. int fa[N];
  90. int main(){
  91. n=read();
  92. for(int i=2; i<=n; ++i) fa[i]=read();
  93. for(int i=1; i<=n; ++i){
  94. a[i]=read();
  95. tr[i].all = tr[i].ans = a[i];
  96. tr[i].siz_sum = tr[i].siz = 1;
  97. }
  98. for(int i=2; i<=n; ++i) link(fa[i],i);
  99. access(1), splay(1);
  100. printf("%.10lf\n",(double)tr[1].ans/n/n);
  101. m=read();
  102. while(m--){
  103. int opt=read(), x=read(), y=read();
  104. if(opt==1){
  105. if(!isAnc(x,y)) cut(fa[x],x), link(y,x), fa[x]=y;
  106. else cut(fa[y],y), link(x,y),fa[y]=x;
  107. access(1), splay(1);
  108. printf("%.10lf\n",(double)tr[1].ans/n/n);
  109. }
  110. else{
  111. makeroot(x), a[x]=y, pushup(x);
  112. printf("%.10lf\n",(double)tr[x].ans/n/n);
  113. }
  114. }
  115. return 0;
  116. }

  过两天复习一波 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. OAUTH协议介绍

    OAUTH协议为用户资源的授权提供了一个安全的.开放而又简易的标准.与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可 ...

  2. frei0r-1.7.0 20191207-0d4b342 DLLs

    https://files.cnblogs.com/files/nlsoft/frei0r-20191207-0d4b342-bin.7z

  3. Mac搭建学习PHP环境

    在sublime text 3中学习PHP,编写PHP代码: 使用的xampp开发环境: 第一步,就是安装xampp,这个没啥可说的,根据自己的系统下载安装就好,我的是OSX;第二步,就是用XAMPP ...

  4. ubuntu安装dockers和images:dvwa

    docker安装 安装前需要更新系统 apt-get update apt-get upgrade apt-get install docker.io 安装完之后就可以试下: docker docke ...

  5. react中component存在性能问题

    Component存在的问题? 1). 父组件重新render(), 当前组件也会重新执行render(), 即使没有任何变化 2). 当前组件setState(), 重新执行render(), 即使 ...

  6. 【OpenJ_Bailian - 2790】迷宫(bfs)

    -->迷宫  Descriptions: 一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由n * n的格点组成,每个格点只有2种状态,.和#,前者表示可以通行后者表示不 ...

  7. 使用URLOS在linux系统中极速部署NFS共享存储服务

    如何在linux系统里搭建NFS服务?其实我们只需要安装一个URLOS面板,然后就能在3分钟内将NFS服务部署完成.近日,URLOS在应用市场中上架了一款NFS应用,它可以让我们的节点主机在3分钟内极 ...

  8. Durable NAND flash memory management

    词条积累 1.NAND flash memory http://www.searchstorage.com.cn/whatis/word_6052.htm http://baike.baidu.com ...

  9. 2019牛客暑期多校训练营(第二场)-D Kth Minimum Clique

    题目链接:https://ac.nowcoder.com/acm/contest/882/D 题意:求给定点权无向图中,点权和第k小的完全子图的点权和.(包括空集) 思路:从空集开始,每找到一个完全子 ...

  10. python多线程学习(一)

    python多线程.多进程 初探 原先刚学Java的时候,多线程也学了几天,后来一直没用到.然后接触python的多线程的时候,貌似看到一句"python多线程很鸡肋",于是乎直接 ...