洛谷题面传送门

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

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

    1.变量的作用域 前提:这里只全部都通过var创建的变量或对象 1.全局变量:函数外创建变量 var x=10; function test(){ alert("全局变量在test函数中&q ...

  2. SpringCloud微服务实战——搭建企业级开发框架(四):集成SpringCloud+SpringBoot

    1.在GitEgg工程的根目录,最上级父pom.xml文件中引入需要依赖的库及Maven插件,设置编码方式: <!--?xml version="1.0" encoding= ...

  3. OO第四单元及学期总结

    OO第四单元及学期总结 第四单元两次作业的架构设计 第一次作业 类图: 树形结构:使用Operation类管理UMLOperation以及parent为该UMLOperation的参数(UMLpara ...

  4. 单片机零基础学习之从“点灯”入门STM32

    本篇文章通过一个简单的例子来熟悉模块化编程以及利用库函数的方法进行开发使用STM32外设的基本流程. 首先,我们打开本讲的例程,在工程目录我们可以看到驱动分组下有 led.delay 两个.c源文件, ...

  5. 21.10.14 test

    题目 WOJ5078 到 WOJ5081 T1 Problem A \(\color{green}{100}\) 由于每轮要选择尽量多的边删除,所以想到无向图的生成树,因为在生成树上再加一条边就会形成 ...

  6. python re:正则表达式中使用变量

    参考:https://www.cnblogs.com/songbiao/p/12422632.html Python中正则表达式的写法,核心就是一个字符串.如下:re.compile(r'表达式')所 ...

  7. error: unsupported reloc 43

    在Ubuntu 16.04.5 LTS编译android 5.1报错 [19:17:13.062]libnativehelper/JniInvocation.cpp:165: error: unsup ...

  8. Linux下文件的三种时间标记:访问时间、修改时间、状态改动时间 (转载)

    在windows下,一个文件有:创建时间.修改时间.访问时间. 而在Linux下,一个文件也有三种时间,分别是:访问时间.修改时间.状态改动时间. 两者有此不同,在Linux下没有创建时间的概念,也就 ...

  9. JAVA笔记4__static关键字/对象数组/foreach/方法的可变参数

    /** * static关键字:修饰属性(实质就是全局变量).方法(无需本类的对象即可调用此方法).类. * 1.static方法只能调用static方法 * 2.static方法只能访问static ...

  10. NOIP模拟88(多校21)

    前言 对于这套题的总体感觉就是难,然后就是自己很菜... 对于 T1 考试时只会一个最垃圾的背包,考完之后对于思路这一块也不是很顺利,大概这就是薄弱的地方吧. 然后 T2 是比较简单的一道题了,但是考 ...