【CF 482E】ELCA
题意



题解
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的更多相关文章
- 【CF#338D】GCD Table
[题目描述] 有一张N,M<=10^12的表格,i行j列的元素是gcd(i,j) 读入一个长度不超过10^4,元素不超过10^12的序列a[1..k],问是否在某一行中出现过 [题解] 要保证g ...
- 【CF#303D】Rotatable Number
[题目描述] Bike是一位机智的少年,非常喜欢数学.他受到142857的启发,发明了一种叫做“循环数”的数. 如你所见,142857是一个神奇的数字,因为它的所有循环排列能由它乘以1,2,...,6 ...
- 【CF 463F】Escape Through Leaf
题意 给你一棵 \(n\) 个点的树,每个节点有两个权值 \(a_i,b_i\). 从一个点 \(u\) 可以跳到以其为根的子树内的任意一点 \(v\)(不能跳到 \(u\) 自己),代价是 \(a_ ...
- 【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 ...
- 【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 ...
- 【35.20%】【CF 706D】Vasiliy's Multiset
time limit per test 4 seconds memory limit per test 256 megabytes input standard input output standa ...
- 【26.8%】【CF 46D】Parking Lot
time limit per test 2 seconds memory limit per test 256 megabytes input standard input output standa ...
- 【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 ...
- 【31.95%】【CF 714B】Filya and Homework
time limit per test 1 second memory limit per test 256 megabytes input standard input output standar ...
随机推荐
- Initializer for conditional binding must have Optional type, not 'String'
今天看到问Swift问题: Initializer for conditional binding must have Optional type, not 'String' 以前没遇到过这个问题, ...
- 小程序报错:出现脚本错误或者未正确调用 Page()的解决办法
场景是两个人共同开发小程序,我使用的是pc端,另一个同事是用的mac端,我这边能够正常运行项目,代码提交到git,同事check下来发现运行报错: 出现脚本错误或者未正确调用 Page(); 如图 一 ...
- 【AMAD】dogpile.cache -- 一个Python缓存API,提供一套通用的接口来适配不同的缓存后端
简介 动机 作用 用法 个人评分 简介 Dogpile1由两套子系统组成,其中一个是基于另一个来构建的. dogpile提供了dogpile lock的概念,这个控制结构让一个线程可以被选为一些资源的 ...
- 我和CMS的往事
CMS(内容管理系统)放在web中理解,也就是我们常说的后台了,用于网站的日常管理.又到期末了,我们的课程——web程序设计,需要提交一份期末大作业,最近需要开发出一个基于JSP的简单web,所以呢, ...
- 【DSP开发】【Linux开发】Linux下PCI设备驱动程序开发
PCI是一种广泛采用的总线标准,它提供了许多优于其它总线标准(如EISA)的新特性,目前已经成为计算机系统中应用最为广泛,并且最为通用的总线标准.Linux的内核能较好地支持PCI总线,本文以Inte ...
- 06: zabbix常见面试题
1.1 zabbix架构 1.zabbix理论 1)Zabbix是一个企业级的.开源的.分布式的监控套件,Zabbix可以监控网络和服务的监控状况. 2)Zabbix利用灵活的告警机制,允许用户对事件 ...
- 前端html+css标签简介(可能就我自己看的懂-。-)
标签集合 # html 文字标签:修改样式 -<font></font> -属性:size:大小,范围1-7,大于7时默认7 color:颜色,英文单词或者十六进制(editp ...
- 14款CSS3图片层叠切换动画
在线演示 本地下载
- 多进程-Pool进程池
from multiprocessing import Pool import os,time def Foo(i): time.sleep(2) print("in process&quo ...
- java实现的LinkedLilst
package javabean.adt.List; import java.util.ConcurrentModificationException; import java.util.Iterat ...