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

传送门



换根类型的统计问题动态点分治都是很好做的。

设所有点的点权和为$sum$

首先,我们先不考虑求$\sum\limits_i s_i^2$,先考虑如何在换根的情况下求$\sum\limits_i s_i$。

考虑一个点$i$会被统计多少次,显然是$dep_i+1$,那么$\sum\limits_i s_i = \sum\limits_i (dep_i+1) \times val_i = \sum\limits_i dep_i \times val_i + sum$。

$\sum\limits_i dep_i \times val_i$是不是似曾相识……其实就是幻想乡战略游戏

接着我们考虑这个烦人的平方项。那么我们还需要推一个结论:换根不会影响$W=\sum\limits_i s_i \times (sum - s_i)$的值。

证明如下:

我们考虑$K = \sum\limits_i \sum\limits_j val_i \times val_j \times dis(i,j)$,意思就是取两个点,将中间的所有边设为两边的点的权值之积然后相加。显然这一个值是不会因为根的变化而改变的。

接着我们考虑一条边$(x,y)$对$K$的贡献,显然是这条边分割开来的两个子树的权值和的乘积,而无论根是哪一个,这两个子树中必定有且仅有一个是树的一个子树,假设$x$对应的树是子树,它就代表着$s_x \times (sum - s_x)$,把所有边的贡献加起来就是$W=\sum\limits_i s_i \times (sum - s_i)=\sum\limits_i \sum\limits_j val_i \times val_j \times dis(i,j)$,所以$W$是不会因为根的变化而变化的。

而$W=\sum\limits_i s_i \times (sum - s_i) = sum \times \sum\limits_i s_i - \sum\limits_i s_i^2$,那么$\sum\limits_i s_i^2 = sum \times \sum\limits_i s_i - W$

接着我们考虑修改点权对$W$的影响。由$\Delta W = \Delta v \times \sum\limits_j val_j \times dep_j$,实质跟上面计算$\sum\limits_i s_i$的方法一样。

  1 #include<bits/stdc++.h>
  2 #define int long long
  3 //This code is written by Itst
  4 using namespace std;
  5
  6 inline int read(){
  7     int a = 0;
  8     bool f = 0;
  9     char c = getchar();
 10     while(c != EOF && !isdigit(c)){
 11         if(c == ‘-‘)
 12             f = 1;
 13         c = getchar();
 14     }
 15     while(c != EOF && isdigit(c)){
 16         a = (a << 3) + (a << 1) + (c ^ ‘0‘);
 17         c = getchar();
 18     }
 19     return f ? -a : a;
 20 }
 21
 22 const int MAXN = 200010;
 23 struct Edge{
 24     int end , upEd;
 25 }Ed[MAXN << 1];
 26 int val[MAXN] , head[MAXN] , fa[MAXN][20] , dis[MAXN][20] , dep[MAXN];
 27 int ST[21][MAXN << 1] , fir[MAXN] , logg2[MAXN << 1] , size[MAXN] , cur[MAXN] , up[MAXN] , sum[MAXN];
 28 int cntEd , N , M , minSize , nowSize , minInd , cntST , W , allV;
 29 bool vis[MAXN];
 30
 31 inline void addEd(int a , int b){
 32     Ed[++cntEd].end = b;
 33     Ed[cntEd].upEd = head[a];
 34     head[a] = cntEd;
 35 }
 36
 37 void init_dfs(int x , int p){
 38     dep[x] = dep[p] + 1;
 39     fir[x] = ++cntST;
 40     sum[x] = val[x];
 41     ST[0][cntST] = x;
 42     for(int i = head[x] ; i ; i = Ed[i].upEd)
 43         if(Ed[i].end != p){
 44             init_dfs(Ed[i].end , x);
 45             ST[0][++cntST] = x;
 46             sum[x] += sum[Ed[i].end];
 47         }
 48     W += sum[x] * (allV - sum[x]);
 49 }
 50
 51 inline int cmp(int a , int b){
 52     return dep[a] < dep[b] ? a : b;
 53 }
 54
 55 void init_st(){
 56     for(int i = 2 ; i <= cntST ; ++i)
 57         logg2[i] = logg2[i >> 1] + 1;
 58     for(int i = 1 ; 1 << i <= cntST ; ++i)
 59         for(int j = 1 ; j + (1 << i) - 1 <= cntST ; ++j)
 60             ST[i][j] = cmp(ST[i - 1][j] , ST[i - 1][j + (1 << (i - 1))]);
 61 }
 62
 63 inline int LCA(int x , int y){
 64     x = fir[x];
 65     y = fir[y];
 66     if(x > y)
 67         swap(x , y);
 68     int t = logg2[y - x + 1];
 69     return cmp(ST[t][x] , ST[t][y - (1 << t) + 1]);
 70 }
 71
 72 inline int calcLen(int x , int y){
 73     return dep[x] + dep[y] - (dep[LCA(x , y)] << 1);
 74 }
 75
 76 void getSize(int x){
 77     vis[x] = 1;
 78     ++nowSize;
 79     for(int i = head[x] ; i ; i = Ed[i].upEd)
 80         if(!vis[Ed[i].end])
 81             getSize(Ed[i].end);
 82     vis[x] = 0;
 83 }
 84
 85 void getRoot(int x){
 86     vis[x] = size[x] = 1;
 87     int maxN = 0;
 88     for(int i = head[x] ; i ; i = Ed[i].upEd)
 89         if(!vis[Ed[i].end]){
 90             getRoot(Ed[i].end);
 91             maxN = max(maxN , size[Ed[i].end]);
 92             size[x] += size[Ed[i].end];
 93         }
 94     maxN = max(maxN , nowSize - size[x]);
 95     if(maxN < minSize){
 96         minSize = maxN;
 97         minInd = x;
 98     }
 99     vis[x] = 0;
100 }
101
102 void init_dfz(int x , int p){
103     nowSize = 0;
104     minSize = 0x7fffffff;
105     getSize(x);
106     getRoot(x);
107     x = minInd;
108     fa[x][0] = x;
109     fa[x][1] = p;
110     vis[x] = 1;
111     sum[x] = val[x];
112     for(int i = 1 ; fa[x][i] ; ++i){
113         fa[x][i + 1] = fa[fa[x][i]][1];
114         dis[x][i] = calcLen(fa[x][i] , x);
115         up[fa[x][i - 1]] += dis[x][i] * val[x];
116         sum[fa[x][i]] += val[x];
117         cur[fa[x][i]] += dis[x][i] * val[x];
118     }
119     for(int i = head[x] ; i ; i = Ed[i].upEd)
120         if(!vis[Ed[i].end])
121             init_dfz(Ed[i].end , x);
122 }
123
124 void init(){
125     init_dfs(1 , 0);
126     init_st();
127     init_dfz(1 , 0);
128 }
129
130 inline int query(int x){
131     int all = cur[x];
132     for(int i = 1 ; fa[x][i] ; ++i){
133         all += cur[fa[x][i]] - up[fa[x][i - 1]];
134         all += (sum[fa[x][i]] - sum[fa[x][i - 1]]) * dis[x][i];
135     }
136     return all;
137 }
138
139 inline void modify(int x , int v){
140     W += (v - val[x]) * query(x);
141     allV += v - val[x];
142     sum[x] += v - val[x];
143     for(int i = 1 ; fa[x][i] ; ++i){
144         up[fa[x][i - 1]] += (v - val[x]) * dis[x][i];
145         cur[fa[x][i]] += (v - val[x]) * dis[x][i];
146         sum[fa[x][i]] += v - val[x];
147     }
148     val[x] = v;
149 }
150
151 signed main(){
152 #ifndef ONLINE_JUDGE
153     freopen("3676.in" , "r" , stdin);
154     freopen("3676.out" , "w" , stdout);
155 #endif
156     N = read();
157     M = read();
158     for(int i = 1 ; i < N ; ++i){
159         int a = read() , b = read();
160         addEd(a , b);
161         addEd(b , a);
162     }
163     for(int i = 1 ; i <= N ; ++i){
164         val[i] = read();
165         allV += val[i];
166     }
167     init();
168     for(int i = 1 ; i <= M ; ++i)
169         if(read() == 1){
170             int x = read() , y = read();
171             modify(x , y);
172         }
173         else
174             cout << allV * (query(read()) + allV) - W << ‘\n‘;
175     return 0;
176 }

原文地址:https://www.cnblogs.com/Itst/p/10085558.html

时间: 2024-10-12 08:25:50

Luogu3676 小清新数据结构题 动态点分治的相关文章

[Luogu3676]小清新数据结构题

题面戳我 题意:给一棵树,树上有点权,每次操作为修改一个点的点权,或者是询问以某个点为根时,每棵子树(以每个点为根,就有n棵子树)点权和的平方和. \(n\le2*10^5\),保证答案在long long范围内 sol 我们设\(s_i\)表示以\(p\)为整棵树的根时,以\(i\)为根的子树的点权和.设\(Sum\)表示所有点的点权和,即\(Sum=\sum_{i=1}^{n}val_i\). 所以这道题给出\(p\),就是要你求\(\sum_{i=1}^{n}s_i^2\). 我们先看\(

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

题目背景 本题时限2s,内存限制256M 题目描述 在很久很久以前,有一棵n个点的树,每个点有一个点权. 现在有q次操作,每次操作是修改一个点的点权或指定一个点,询问以这个点为根时每棵子树点权和的平方和. (题目不是很好懂,没看太懂的可以看看样例解释) 输入输出格式 输入格式: 第一行两个整数n.q. 接下来n-1行每行两个整数a和b,表示树中a与b之间有一条边,保证给出的边不会重复. 接下来一行n个整数,第i个整数表示第i个点的点权. 接下来q行每行两或三个数,如果第一个数为1,那么接下来有两

【luoguP3676】 小清新数据结构题 推式子(LCT维护)

挺有趣的一个数据结构题,不必用 LCT 维护,只不过 LCT 比较好写 ~ code: #include <cstdio> #include <string> #include <cstring> #include <algorithm> #define N 200008 #define ll long long #define lson s[x].ch[0] #define rson s[x].ch[1] using namespace std; ll a

Luogu 3676 小清新数据结构题

推荐博客: http://www.cnblogs.com/Mychael/p/9257242.html 感觉还挺好玩的 首先考虑以1为根,把每一个点子树的权值和都算出来,记为$val_{i}$,那么在所有操作都没有开始的时候(以$1$为根的)$ans_{1} = \sum_{i= 1}^{n}val_{i}^{2}$ 考虑到一个修改的操作只会对修改的点$x$到根($1$)链上的点产生影响,那么一次修改只要修对这条树链上的点增加$v - a_{x}$(假设修改后的值为$v$)就好了. 链剖之后线段

lp3676 小清新数据结构题

传送门 Description 有一棵\(n\)个点的树,每个点有一个点权. 现在有\(q\)次操作,每次操作是修改一个点的点权或指定一个点,询问以这个点为根时每棵子树点权和的平方和. Solution 我们设\(Sum=\sum_{i=1}^{n} w_i\),\(s_i\)表示\(i\)子树的权值和 发现不管根是哪个节点,\(W=\sum_{i=1}^n s_i(Sum-s_i)\)都是一个定值 因为它相当于对于每条边连接的两个联通块的"点权和的积"的和 所以,我们要求的\(\su

小清新签到题(DP)

传送门 然鹅我并不觉得这道题很清新rua 思维巧妙!(参考) 对于第k小,我们可以这样考虑,若是第k小,那么比它小的方案应该是有k-1个. 在排列组合中,若固定i放在j位置,方案数是确定的,即:i固定在j位置,满足这个条件的序列的rank是在一个范围内的. 对于逆序对 常见思考方式是从小到大枚举数字,考虑对逆序对个数做出的贡献(从小到大插入,后插入不会对前插入造成影响) 设f[i][j]为i个数,j个逆序对的方案数,可得转移方程为 f[i][j]=f[i-1][j]+f[i-1][j-1]+……

动态存储管理-第8章-《数据结构题集》习题解析-严蔚敏吴伟民版

习题集解析部分 第8章 动态存储管理 ——<数据结构题集>-严蔚敏.吴伟民版        源码使用说明  链接??? <数据结构-C语言版>(严蔚敏,吴伟民版)课本源码+习题集解析使用说明        课本源码合辑  链接??? <数据结构>课本源码合辑        习题集全解析  链接??? <数据结构题集>习题解析合辑       相关测试数据下载  链接? 数据包       本习题文档的存放目录:数据结构\▼配套习题解析\▼08 动态存储管理

动态点分治学习笔记

学习动态点分治之前要先弄清楚点分治的原理,二者的应用范围的不同就在于动态的支持在线修改操作,而实现的不同就在于动态点分治要建点分树. OI中有很多树上统计问题,这类问题往往都有一个比较容易实现的暴力做法,而用高级数据结构维护信息有显得过于复杂,有没有一种"优美的暴力",能既保证思维的简单性,又有更高效的时间复杂度保证呢?这就是点分治的思想. 点分治的实现过程是:每次找到当前树的重心,然后以这个重心为根统计这个树的信息,然后对重心的每个孩子分别递归,同样用将重心作为根的方法统计子树的信息

[ZJOI2007]捉迷藏 解题报告 (动态点分治)

[ZJOI2007]捉迷藏 近期做过的码量最大的一题 (当然也是我写丑了....) 题意 有一个 \(n\) 个节点的树 (\(n \le 10^5\)), 每个节点为黑色或白色. 有 \(m\) 个操作 (\(m \le 5 \times 10^5\)), 操作有两种, 将点 \(x\) 的的颜色翻转. 查询树上距离最远的黑色点对之间的距离. 思路 首先, 如果没有修改操作的话, 就是一个裸的点分治 (点分治学习笔记). 有修改操作, 那就 动态点分治. 动态点分治的基本思路是 (个人总结的)