CF960H 题解

题意简述

注:原题翻译有点小锅,题目中的 $b$ 和 $c$ 应该是一个东西。本文暂且写作 $c$。

给出一棵 $n(2\le n\le 5\times 10^4)$ 个节点的有根树,根为 $1$。点 $2\sim n$ 的父亲为 $p_2\sim p_n$。有 $m(1\le m\le 5\times 10^4)$ 种颜色。点 $i$ 的颜色为 $f_i(1\le f_i\le m)$,颜色 $i$ 的权值为 $c_i(1\le c_i\le 100)$。

有两种操作共 $q(1\le q\times 5\times 10^4)$ 次:

  • $\texttt{1 }x\text{ }y$,将 $f_x\leftarrow y$。

  • $\texttt{2 }x$,从树中等概率选取一个点 $i$,得到 $(S_ic_x-C)^2$ 的价值。求期望价值。其中 $S_i$ 表示以 $i$ 为根的子树中颜色为 $x$ 的节点数。$C$ 是给定的常数。

题目分析

一个比较裸的树剖,甚至只有路径修改单点查询。

先分析操作 $2$。这一询问实际上是求 $\displaystyle \frac{1}{n}\sum_{i=1}^n(S_ic_x-C)^2$。直接维护不太可行,因此我们对它进行一下转换,把不同次数的项分解开来维护(类似于方差这个题):

$\ \ \ \ \displaystyle\frac{1}{n}\sum_{i=1}^n(S_ic_x-C)^2$

$\displaystyle=\frac{1}{n}\sum_{i=1}^n(S_i^2c_x^2-2S_ic_xC+C^2)$

$\displaystyle=\frac{1}{n}\sum_{i=1}^nS_i^2c_x^2-\frac{1}{n}\sum_{i=1}^n2S_ic_xC+C^2$

$\displaystyle=\frac{c_x^2}{n}\sum_{i=1}^nS_i^2-\frac{2c_xC}{n}\sum_{i=1}^nS_i+C^2$

$c_x$ 和 $C$ 都是固定常数,也就是说我们只用维护 $\displaystyle\sum_{i=1}^nS_i^2$ 和 $\displaystyle\sum_{i=1}^nS_i$ 就可以,这两个量的合并都十分直观(还是类似方差这个题)。

再看操作 $1$。这一修改实质上只涉及到从 $x$ 到根的路径上的结点,就是典型的路径修改。只需要把这条路径上旧颜色的 $S$ 减 $1$,新颜色的 $S$ 加 $1$ 即可。

注意到颜色数不多,很容易想到重链剖分后直接维护 $m$ 棵线段树。但是构造完整的线段树结构肯定会 MLE(空间复杂度 $O(nm)$),所以考虑动态开点。空间复杂度降到 $O(m \log^2 n)$,此题空间限制最大能开到 $O(1.6\times 10^7)$,已经足够了。

代码实现

#include<bits/stdc++.h>
using namespace std;
int n,m,q,op,x,y,f[50010],b[50010],a[50010],nd,num[50010];
int tot,hd[50010],nt[50010],v[50010];
int cnt,d[50010],top[50010],fa[50010],dfn[50010],son[50010],size[50010];
long long C;
struct node
{
    int ls,rs;//左右子结点编号。 
    long long sqsum,sum,tag;//平方和、和、懒标记。 
}tr[16000010];//线段树结点结构体 
void rd(int &x)
{
    x=0;
    char c=getchar();
    for(;c>'9'||c<'0';c=getchar());
    for(;c<='9'&&c>='0';c=getchar())
        x=(x<<3)+(x<<1)+c-'0';
}//快读 
void rd(long long &x)
{
    x=0ll;
    char c=getchar();
    for(;c>'9'||c<'0';c=getchar());
    for(;c<='9'&&c>='0';c=getchar())
        x=(x<<3ll)+(x<<1ll)+c-'0';
}//快读
void add(int x,int y)
{
    v[++tot]=y;
    nt[tot]=hd[x];
    hd[x]=tot;
}//建边 
void dfs1(int x)
{
    size[x]=1;
    d[x]=d[fa[x]]+1;
    for(int i=hd[x];i;i=nt[i])
    {
        int y=v[i];
        dfs1(y);
        size[x]+=size[y];
        if(size[y]>size[son[x]])
            son[x]=y;
    }
}//第一遍 DFS,计算出结点深度 d、子树大小 size 并找到重儿子 son。 
void dfs2(int x,int tp)
{
    top[x]=tp;
    dfn[x]=++cnt;
    if(!son[x])
        return;
    dfs2(son[x],tp);
    for(int i=hd[x];i;i=nt[i])
    {
        int y=v[i];
        if(y!=son[x])
            dfs2(y,y);
    }
}//第二遍 DFS,计算出重链顶 top、遍历顺序 dfn。 
void pushup(int p)
{
    tr[p].sqsum=tr[tr[p].ls].sqsum+tr[tr[p].rs].sqsum;//平方和更新 
    tr[p].sum=tr[tr[p].ls].sum+tr[tr[p].rs].sum;//和更新 
}//用子结点更新父结点。 
void addtag(int &p,long long val,int len)
{
    if(!p)
        p=++nd;//原来没有就新建。 
    tr[p].sqsum+=2ll*tr[p].sum*val+1ll*len*val*val;//平方和更新 
    tr[p].sum+=1ll*len*val;//和更新 
    tr[p].tag+=val;//标记更新 
}//打标记。 
void pushdown(int &p,int len)
{
    if(tr[p].tag)
    {
        addtag(tr[p].ls,tr[p].tag,(len+1)/2);//左儿子 
        addtag(tr[p].rs,tr[p].tag,len/2);//右儿子 
        tr[p].tag=0;//清空标记 
    }
}//下传懒标记。 
void change(int ql,int qr,int &p,int l,int r,long long val) //ql:当前结点左端点,qr:当前结点右端点,p:当前结点,l:询问左端点,r:询问右端点,val:更新的值。 
{
    if(!p)
        p=++nd;//原来没有就新建。 
    if(ql>=l&&qr<=r)//全包含直接修改。 
    {
        addtag(p,val,qr-ql+1);
        return;
    }
    pushdown(p,qr-ql+1); 
    int mid=ql+qr>>1;
    if(mid>=l)
        change(ql,mid,tr[p].ls,l,r,val);//左子结点 
    if(mid<r)
        change(mid+1,qr,tr[p].rs,l,r,val);
    pushup(p);
}//线段树区间修改。 
void change_to_root(int x,int col,long long val)//从 x 修改到根。 
{
    while(top[x]!=1)
    {
        change(1,n,num[col],dfn[top[x]],dfn[x],val);
        x=fa[top[x]];//一条重链一条重链地改 
    }
    change(1,n,num[col],1,dfn[x],val);
}
int main()
{
    rd(n),rd(m),rd(q),rd(C);
    for(int i=1;i<=n;i++)
        rd(a[i]);
    for(int i=2;i<=n;i++)
        rd(fa[i]),add(fa[i],i);
    for(int i=1;i<=m;i++)
        rd(b[i]);
    dfs1(1);
    dfs2(1,1);
    for(int i=1;i<=n;i++)
        change_to_root(i,a[i],1);//初始化 
    while(q--)
    {
        rd(op),rd(x);
        if(op==1)
        {
            rd(y);
            change_to_root(x,a[x],-1);//把原来颜色的删掉 1 
            a[x]=y;
            change_to_root(x,a[x],1);//新颜色加上 1
        }           
        else
            printf("%.10lf\n",(double)(b[x]*b[x]*tr[num[x]].sqsum-2*b[x]*C*tr[num[x]].sum)/n+C*C);
    }
    return 0;
}
posted @ 2023-07-24 23:02  Hadtsti  阅读(0)  评论(0编辑  收藏  举报  来源