洛谷题面传送门

题目名称好评(实在是太清新了呢)

首先考虑探究这个“换根操作”有什么性质。我们考虑在换根前后虽然每个点的子树会变,但整棵树的形态不会边,换句话说,割掉每条边后,得到的两个子树的中点权之和不会变,因此我们考虑将这个东西与平方和挂钩。考虑构造 \(S=\sum\limits_{i=1}^nsiz_i(sum-siz_i)\),其中 \(siz_i\) 为 \(i\) 子树内所有点点权之和,\(sum\) 为所有点点权之和。那么不难发现 \(S\) 就是断掉所有点之后形成的两个连通块的点权之和的乘积之和,根据之前的推论这东西不随根的改变而改变,也就是说它是一个定值。

考虑化简这个式子搞出一个 \(\sum\limits_{i=1}^nsiz_i^2\) 出来。上式可以改写为 \(\sum\limits_{i=1}^nsiz_i^2=sum·\sum\limits_{i=1}^nsiz_i-S\),因此我们只用对于每个根求出 \(\sum\limits_{i=1}^nsiz_i\),以及 \(S\) 的值即可求出答案。首先是减号前的 \(\sum\limits_{i=1}^nsiz_i\)。不难发现如果我们对一个点 \(x\) 的权值 \(+v\),会导致点 \(y\) 的 \(\sum\limits_{i=1}^nsiz_i\) 增加 \(v·(\text{dist}(x,y)+1)\),考虑动态点分治维护这个东西,先建出点分树,然后在点分树上跳祖先,假设 \(x\) 在点分树上存在一个祖先 \(y\),它在点分树上的父亲为 \(f\),那么 \(x\) 的这次修改对在 \(f\) 的子树内,却不在 \(y\) 子树内的点 \(z\) 的影响是,点 \(z\) 的答案加上 \((\text{dist}(z,f)+\text{dist}(f,x)+1)·v\),维护三个标记 \(mk1_x,mk2_x,mk3_x\),分别表示 \(x\) 会对其点分树子树内的点 \(y\) 的答案产生 \(\text{dist}(x,y)·mk1_x,\text{dist}(fa_x,y)·mk2_x,mk3_x\) 的贡献,这样通过 \(\mathcal O(n\log n)-\mathcal O(1)\) 距离即可在 \(\mathcal O(\log n)\) 的时间内对于一个根,求出 \(\sum\limits_{i=1}^nsiz_i\)。接下来是 \(S\) 的维护。我比较 sb 所以直接树剖+线段树维护以 \(1\) 为根时的答案,具体做法大概就是 DFS 序+线段树,考虑什么情况下对 \(x\) 加 \(v\) 会导致 \(siz\) 加 \(v\),什么情况下会对 \(sum-siz\) 加 \(v\),然后线段树维护一下即可,这里不再赘述。事实上还有更简洁的组合意义做法。对于一条边,割掉这条边后两个子树的大小的乘积,可以看作对于所有 \((u,v)\) 路径经过这条边的点对 \((u,v)\) 的 \(w_uw_v\) 之和除以 \(2\),因此 \(S=\dfrac{1}{2}·\sum\limits_{u,v}\text{dist}(u,v)·w_uw_v\),这东西也可以动态点分治。

如果两个式子的维护方式都用动态点分治实现的话那复杂度大概是 \(n\log n\) 的。而由于 wssb 所以我的程序复杂度是 2log 的,还码了 180+ 行

const int MAXN=2e5;
const int INF=0x3f3f3f3f;
const int LOG_N=19;
int n,qu,hd[MAXN+5],to[MAXN*2+5],nxt[MAXN*2+5],ec=0,a[MAXN+5];
void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
int dep[MAXN+5],sz[MAXN+5],wson[MAXN+5],fa[MAXN+5],top[MAXN+5];
int dfn[MAXN+5],tim,dfn_eu[MAXN+5],tim_eu;
pii st[MAXN*2+5][LOG_N+2];
void dfs1(int x,int f){
fa[x]=f;sz[x]=1;
st[dfn_eu[x]=++tim_eu][0]=mp(dep[x],x);
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f) continue;dep[y]=dep[x]+1;
dfs1(y,x);st[dfn_eu[x]=++tim_eu][0]=mp(dep[x],x);
sz[x]+=sz[y];if(sz[y]>sz[wson[x]]) wson[x]=y;
}
}
void dfs2(int x,int tp){
dfn[x]=++tim;top[x]=tp;if(wson[x]) dfs2(wson[x],tp);
for(int e=hd[x];e;e=nxt[e]) if(to[e]!=fa[x]&&to[e]!=wson[x])
dfs2(to[e],to[e]);
}
pii query_st(int l,int r){
int k=31-__builtin_clz(r-l+1);
return min(st[l][k],st[r-(1<<k)+1][k]);
}
int getlca(int x,int y){
x=dfn_eu[x];y=dfn_eu[y];if(x>y) swap(x,y);
return query_st(x,y).se;
}
int getdis(int x,int y){return dep[x]+dep[y]-(dep[getlca(x,y)]<<1);}
struct node{int l,r;ll sum,s1,s2,lz1,lz2;} s[MAXN*4+5];
void pushup(int k){
s[k].s1=s[k<<1].s1+s[k<<1|1].s1;
s[k].s2=s[k<<1].s2+s[k<<1|1].s2;
s[k].sum=s[k<<1].sum+s[k<<1|1].sum;
}
void build(int k,int l,int r){
s[k].l=l;s[k].r=r;if(l==r) return;int mid=l+r>>1;
build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void tag(int k,ll v1,ll v2){
s[k].sum+=1ll*v2*s[k].s1+1ll*v1*s[k].s2;
s[k].sum+=1ll*(s[k].r-s[k].l+1)*v1*v2;
s[k].s1+=v1*(s[k].r-s[k].l+1);
s[k].s2+=v2*(s[k].r-s[k].l+1);
s[k].lz1+=v1;s[k].lz2+=v2;
}
void pushdown(int k){
if(s[k].lz1||s[k].lz2){
tag(k<<1,s[k].lz1,s[k].lz2);
tag(k<<1|1,s[k].lz1,s[k].lz2);
s[k].lz1=s[k].lz2=0;
}
}
void modify(int k,int l,int r,int v1,int v2){
if(l>r) return;
if(l<=s[k].l&&s[k].r<=r) return tag(k,v1,v2),void();
pushdown(k);int mid=s[k].l+s[k].r>>1;
if(r<=mid) modify(k<<1,l,r,v1,v2);
else if(l>mid) modify(k<<1|1,l,r,v1,v2);
else modify(k<<1,l,mid,v1,v2),modify(k<<1|1,mid+1,r,v1,v2);
pushup(k);
}
int mx[MAXN+5],cent,siz[MAXN+5];bool vis[MAXN+5];
void findcent(int x,int f,int totsiz){
mx[x]=0;siz[x]=1;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(y==f||vis[y]) continue;
findcent(y,x,totsiz);siz[x]+=siz[y];
chkmax(mx[x],siz[y]);
} chkmax(mx[x],totsiz-siz[x]);
if(mx[cent]>mx[x]) cent=x;
}
int fa_t[MAXN+5];
void divcent(int x){
vis[x]=1;
for(int e=hd[x];e;e=nxt[e]){
int y=to[e];if(vis[y]) continue;
cent=0;findcent(y,x,siz[y]);//orz lca!
// printf("cdt %d %d\n",cent,x);
fa_t[cent]=x;divcent(cent);
}
}
void prework(){
dfs1(1,0);dfs2(1,1);mx[0]=INF;findcent(1,0,n);divcent(cent);
for(int i=1;i<=LOG_N;i++) for(int j=1;j+(1<<i)-1<=n+n;j++)
st[j][i]=min(st[j][i-1],st[j+(1<<i-1)][i-1]);
build(1,1,n);
}
void jumpath(int x,int v){
while(x){
modify(1,dfn[top[x]],dfn[x],v,-v);
x=fa[top[x]];
}
}
ll mark[MAXN+5],mark_f[MAXN+5],mark0[MAXN+5],tot=0;
void add(int x,int v){
modify(1,1,n,0,v);jumpath(x,v);
static int stk[LOG_N+3];int tp=0;
for(int y=x;y;y=fa_t[y]) stk[++tp]=y;
// printf("tp=%d\n",tp);
reverse(stk+1,stk+tp+1);
for(int i=1;i<=tp;i++){
mark[stk[i]]+=v;mark0[stk[i]]+=1ll*v*getdis(x,stk[i]);
if(i^tp) mark_f[stk[i+1]]-=v,mark0[stk[i+1]]-=1ll*v*getdis(x,stk[i]);
}
tot+=v;
}
ll query(int x){
ll res=0;
static int stk[LOG_N+3];int tp=0;
for(int y=x;y;y=fa_t[y]) stk[++tp]=y;
reverse(stk+1,stk+tp+1);
for(int i=1;i<=tp;i++){
res+=mark[stk[i]]*getdis(x,stk[i]);
// printf("%d %lld %lld %lld\n",stk[i],mark[stk[i]],mark_f[stk[i]],mark0[i]);
if(i^1) res+=mark_f[stk[i]]*getdis(x,stk[i-1]);
res+=mark0[stk[i]];
} //printf("%lld\n",s[1].sum);
// printf("%lld\n",tot);
return (res+tot)*tot-s[1].sum;
}
int main(){
scanf("%d%d",&n,&qu);
for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),adde(u,v),adde(v,u);
prework();//printf("%d\n",getdis(2,3));
for(int i=1;i<=n;i++) scanf("%d",&a[i]),add(i,a[i]);
while(qu--){
int opt;scanf("%d",&opt);
if(opt==1){
int x,v;scanf("%d%d",&x,&v);
add(x,-a[x]);a[x]=v;add(x,a[x]);
} else {
int x;scanf("%d",&x);
printf("%lld\n",query(x));
}
}
return 0;
}

洛谷 P3676 - 小清新数据结构题(动态点分治)的更多相关文章

  1. 洛谷P3676 小清新数据结构题 [动态点分治]

    传送门 思路 这思路好妙啊! 首先很多人都会想到推式子之后树链剖分+线段树,但这样不够优美,不喜欢. 脑洞大开想到这样一个式子: \[ \sum_{x} sum_x(All-sum_x) \] 其中\ ...

  2. 洛谷P3676 小清新数据结构题 【树剖 + BIT】

    题目链接 洛谷P3676 题解 我们先维护\(1\)为根的答案,再考虑换根 一开始的答案可以\(O(n)\)计算出来 考虑修改,记\(s[u]\)表示\(u\)为根的子树的权值和 当\(u\)节点产生 ...

  3. 【刷题】洛谷 P3676 小清新数据结构题

    题目背景 本题时限2s,内存限制256M 题目描述 在很久很久以前,有一棵n个点的树,每个点有一个点权. 现在有q次操作,每次操作是修改一个点的点权或指定一个点,询问以这个点为根时每棵子树点权和的平方 ...

  4. 洛谷P3676 小清新数据结构题(动态点分治+树链剖分)

    传送门 感觉这题做下来心态有点崩……$RMQ$求$LCA$没有树剖快我可以理解为是常数太大……然而我明明用了自以为不会退化的点分然而为什么比会退化的点分跑得反而更慢啊啊啊啊~~~ 先膜一波zsy大佬 ...

  5. 洛谷 P3676 小清新数据结构题

    https://www.luogu.org/problemnew/show/P3676 这题被我当成动态dp去做了,码了4k,搞了一个换根的动态dp #include<cstdio> #i ...

  6. Luogu3676 小清新数据结构题 动态点分治

    传送门 换根类型的统计问题动态点分治都是很好做的. 设所有点的点权和为$sum$ 首先,我们先不考虑求$\sum\limits_i s_i^2$,先考虑如何在换根的情况下求$\sum\limits_i ...

  7. 洛谷 P3672 小清新签到题 [DP 排列]

    传送门 题意:给定自然数n.k.x,你要求出第k小的长度为n的逆序对对数为x的1~n的排列 $n \le 300, k \le 10^13$ 一下子想到hzc讲过的DP 从小到大插入,后插入不会对前插 ...

  8. [P3676]小清新数据结构题

    Description: 给你一棵树,每次询问以一个点为根时所有子树点权和的平方和 带修改 Hint: \(n\le 2*10^5\) Solution: 这题只要推出式子就很简单了 如果不换根这个平 ...

  9. [洛谷P3672]小清新签到题

    题目描述 题目还是简单一点好. 给定自然数n.k.x,你要求出第k小的长度为n的逆序对对数为x的1~n的排列a1,a2...an,然后用仙人图上在线分支定界启发式带花树上下界最小费用流解决问题,保证存 ...

随机推荐

  1. 痞子衡嵌入式:超级下载算法RT-UFL v1.0在IAR EW for Arm下的使用

    痞子衡主导的"学术"项目 <RT-UFL - 一个适用全平台i.MXRT的超级下载算法设计> v1.0 版发布近 4 个月了,部分客户已经在实际项目开发调试中用上了这个 ...

  2. VS Code C/C++开发环境配置

    VS Code C/C++开发环境配置 一.安装 ​ 1.前往官网下载安装即可 https://code.visualstudio.com/ ​ 2.进入VS Code安装如下插件 二.C/C++开发 ...

  3. Linux服务器装Anaconda&TensorFlow

    远程Linux服务器装Anaconda&指定版本TensorFlow 说明: 由于疫情影响,原先使用的服务器已断电,故重选了一台服务器对环境重选进行搭建,正好补上这篇博文. 01 下载Anac ...

  4. [敏捷软工团队博客]Beta设计和计划

    项目 内容 2020春季计算机学院软件工程(罗杰 任健) 博客园班级博客 作业要求 Beta设计和计划 我们在这个课程的目标是 在团队合作中锻炼自己 这个作业在哪个具体方面帮助我们实现目标 对Beta ...

  5. OO电梯作业总结

    (一)第五次作业 一.设计思路 生产消费者模型,输入接口是producer,调度器是tray,电梯是customer.由于只有一架电梯,所以生产消费模型满足以下条件: 一个生产者,一个消费者 托盘不为 ...

  6. Linux内核漏洞精准检测如何做?SCA工具不能只在软件层面

    摘要:二进制SCA工具要想更好的辅助安全人员实现安全审计.降低漏洞检测的误报率,必须向更细颗粒度的检测维度发展,而不仅仅停留在开源软件的层面,同时对漏洞库的要求也需要向细颗粒度的精准信息提出的挑战. ...

  7. 疯狂Java基础Day2

    巩固Java流程控制的学习... 一.用户交互Scanner 通过Scanner类获取用户的输入 import java.util.Scanner; public class Demo1 { publ ...

  8. Get_init_color_map

    #!/bin/bash./simulate_screencap.sh./analysis_screencap.py

  9. ELK集群之logstash(5)

    Logstash工作原理   Logstash事件处理有三个阶段:inputs → filters → outputs.是一个接收,处理,转发日志的工具.支持系统日志,webserver日志,错误日志 ...

  10. RISCV 入门 (学习笔记)

    文章目录 1. risv 相关背景 1.1 arm 授权费 1.2 riscv 发展历史 1.3 riscv 风险 2. 指令集 2.1 可配置的通用寄存器组 2.2 规整的指令编码 2.3 简洁的存 ...