洛谷 P3676 - 小清新数据结构题(动态点分治)
题目名称好评(实在是太清新了呢)
首先考虑探究这个“换根操作”有什么性质。我们考虑在换根前后虽然每个点的子树会变,但整棵树的形态不会边,换句话说,割掉每条边后,得到的两个子树的中点权之和不会变,因此我们考虑将这个东西与平方和挂钩。考虑构造 \(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 - 小清新数据结构题(动态点分治)的更多相关文章
- 洛谷P3676 小清新数据结构题 [动态点分治]
传送门 思路 这思路好妙啊! 首先很多人都会想到推式子之后树链剖分+线段树,但这样不够优美,不喜欢. 脑洞大开想到这样一个式子: \[ \sum_{x} sum_x(All-sum_x) \] 其中\ ...
- 洛谷P3676 小清新数据结构题 【树剖 + BIT】
题目链接 洛谷P3676 题解 我们先维护\(1\)为根的答案,再考虑换根 一开始的答案可以\(O(n)\)计算出来 考虑修改,记\(s[u]\)表示\(u\)为根的子树的权值和 当\(u\)节点产生 ...
- 【刷题】洛谷 P3676 小清新数据结构题
题目背景 本题时限2s,内存限制256M 题目描述 在很久很久以前,有一棵n个点的树,每个点有一个点权. 现在有q次操作,每次操作是修改一个点的点权或指定一个点,询问以这个点为根时每棵子树点权和的平方 ...
- 洛谷P3676 小清新数据结构题(动态点分治+树链剖分)
传送门 感觉这题做下来心态有点崩……$RMQ$求$LCA$没有树剖快我可以理解为是常数太大……然而我明明用了自以为不会退化的点分然而为什么比会退化的点分跑得反而更慢啊啊啊啊~~~ 先膜一波zsy大佬 ...
- 洛谷 P3676 小清新数据结构题
https://www.luogu.org/problemnew/show/P3676 这题被我当成动态dp去做了,码了4k,搞了一个换根的动态dp #include<cstdio> #i ...
- Luogu3676 小清新数据结构题 动态点分治
传送门 换根类型的统计问题动态点分治都是很好做的. 设所有点的点权和为$sum$ 首先,我们先不考虑求$\sum\limits_i s_i^2$,先考虑如何在换根的情况下求$\sum\limits_i ...
- 洛谷 P3672 小清新签到题 [DP 排列]
传送门 题意:给定自然数n.k.x,你要求出第k小的长度为n的逆序对对数为x的1~n的排列 $n \le 300, k \le 10^13$ 一下子想到hzc讲过的DP 从小到大插入,后插入不会对前插 ...
- [P3676]小清新数据结构题
Description: 给你一棵树,每次询问以一个点为根时所有子树点权和的平方和 带修改 Hint: \(n\le 2*10^5\) Solution: 这题只要推出式子就很简单了 如果不换根这个平 ...
- [洛谷P3672]小清新签到题
题目描述 题目还是简单一点好. 给定自然数n.k.x,你要求出第k小的长度为n的逆序对对数为x的1~n的排列a1,a2...an,然后用仙人图上在线分支定界启发式带花树上下界最小费用流解决问题,保证存 ...
随机推荐
- 如何查找一个目录中所有c文件的总行数
如何查找一个目录中所有c文件的行数 面试题问到了一题,如何统计wc文件夹下所有文件的行数,包括了子目录. 最后在 https://blog.csdn.net/a_ran/article/details ...
- 这12种场景Spring事务会失效!
前言 对于从事java开发工作的同学来说,spring的事务肯定再熟悉不过了.在某些业务场景下,如果一个请求中,需要同时写入多张表的数据.为了保证操作的原子性 (要么同时成功,要么同时失败),避免数据 ...
- Egg.js学习与实战系列 · Post请求`csrf token`问题
在使用axios请求egg.js封装的post接口时出现missing csrf token 或 invalid csrf token.踩过坑的新手估计不在少数,本篇记录一下解决方法. 问题原因 引用 ...
- SpringCloud微服务实战——搭建企业级开发框架(七):自定义通用响应消息及统一异常处理
平时开发过程中,无可避免我们需要处理各类异常,所以这里我们在公共模块中自定义统一异常,Spring Boot 提供 @RestControllerAdvice 注解统一异常处理,我们在GitEgg ...
- RF射频传输,原理介绍,三分钟看懂!发射功率、接收灵敏度详解!
射频是什么? 官方说法:RF,Radio Frequency. (不懂的人,看了还是不懂,不过对于物联网行业的开发工程师.产品经理和项目经理,还是有需要对射频有个基础了解的.) 燚智能解读: 两个人, ...
- OpenWrt编译问题记录
错误一.config.status: error: cannot find input file: `xmetadataretriever/Makefile.in' configure: creati ...
- 阿里云ECI如何6秒扩容3000容器实例?
引言 根据最新CNCF报告,有超过90%的用户在生产环境使用容器,并且有超过80%的用户通过Kubernetes管理容器.是不是我们的生产环境上了K8s就完美解决了应用部署的问题?IT界有句俗语,没有 ...
- 重新整理 .net core 实践篇——— filter[四十四]
前言 简单介绍一下filter 正文 filter 的种类,微软文档中写道: 每种筛选器类型都在筛选器管道中的不同阶段执行: 授权筛选器最先运行,用于确定是否已针对请求为用户授权. 如果请求未获授权, ...
- docker容器命令(一)
容器命令 创建容器:docker run 参数: -it 交互 -d 后台 –name 容器名 -p 主机端口:容器端口 (主机端口映射到docker端口) docker run --name cen ...
- 基于霸道秉火的STM32F103ZET6嵌入式开发之------定时器中断3
1 #include "time.h" 2 #include "led.h" 3 #include "beep.h" 4 //¶¨Ê±Æ÷Ö ...