洛谷题面传送门

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

首先考虑探究这个“换根操作”有什么性质。我们考虑在换根前后虽然每个点的子树会变,但整棵树的形态不会边,换句话说,割掉每条边后,得到的两个子树的中点权之和不会变,因此我们考虑将这个东西与平方和挂钩。考虑构造 \(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. c语言中一条竖线是什么符号?

    "|"在C语言中表示按位或,是双目运算符.其功能是参与运算的两数各对应的二进位(也就是最后一位)相或.只要对应的二个二进位有一个为1时,结果位就为1.参与运算的两个数均以补码出现. ...

  2. 免费 CDN 玩法 —— 文件一键上传到 NPM

    前言 unpkg.jsdelivr 等站点可加速 NPM 包文件,适合作为个人网站或演示案例的免费 CDN. 虽然上传文件到 NPM 很简单,创建 package.json 然后 npm publis ...

  3. 初学python-day3 列表

  4. Shadertoy 教程 Part 1 - 介绍

    Note: This series blog was translated from Nathan Vaughn's Shaders Language Tutorial and has been au ...

  5. beta事后分析

    设想和目标 1. 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 软件要解决的问题是是开发一个简易方便,为用户带来便捷且功能齐全的表情包管理小程序: 预期的典型用户 ...

  6. the Agiles Scrum Meeting 9

    会议时间:2020.4.17 20:00 1.每个人的工作 今天已完成的工作 个人结对项目增量开发组:基本实现个人项目创建.仓库自动分配,修复bug issues: 增量组:准备评测机制,增加仓库自动 ...

  7. ST表 ----kzsn考挂后有感

    ST表,一个十分神奇的东西,需要O(nlogn)的时间预处理,但是他查询只需要O(1). 看似与线段树等数据结构时间复杂度一样,但是ST表的复杂度只在于预处理,预处理之后可以当做不耗时! 而想线段树这 ...

  8. TT模板的作用及使用

    一.假如你在ef中添加一个实体,没有模板,你需要在DAL层中新建一个"莫某Dal"和"I某某Dal"以及在公共的DbSession中加你的这个dal,然后需要在 ...

  9. hdu 5018 Revenge of GCD

    题意: 给你两个数:X和Y  .输出它们的第K大公约数.若不存在输出 -1 数据范围: 1 <= X, Y, K <= 1 000 000 000 000 思路: 它俩的公约数一定是gcd ...

  10. DeWeb 简介

    DeWeb是一个可以直接将Delphi程序快速转换为网页应用的工具! 使用DeWeb, 开发者不需要学习HTML.JavaScript.Java.PHP.ASP.C#等新知识,用Delphi搞定一切. ...