题目链接:
题目大意:
给定一个树,每条边连接a和b,每一条边可能有两种操作:
1.以a为起点并且不经过b的所有点权值+x
2.以b为起点并且不经过a的所有点权值+x
输出最终所有点的权值
学习笔记:
这道题主要用了树上差分的方法,它实际上是把前缀和思想引入图问题的一种方法。首先我们知道,前缀和必须是在有顺序的情况下用的,而无根树本身是没有顺序的,所以必须先把无根数转换成有根树结构。有根树是从父节点遍历到子结点的,这样就有了顺序。
再看到这道题,a和b是相连的,假如说a是b的父节点。假如要让b部分(以b为起点,不经过a的所有点)加x,那么就让val[b] += x即可,假如要让a部分(以a为起点,不经过b的所有点)加x,可以在全局范围内取b部分的补集,因为除了b部分就是a部分,我们让val[1] += x,val[b] -= x就完成了取反。(直接寻找a部分比较困难,就想到了取补集的方法)
代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <iomanip>
#include <string.h>
#include <climits>
using namespace std;
#define int long long
const int maxn = 2e5+5;
vector<int> g[maxn]; // g[i]用来存和i相连的所有点
int val[maxn]; // 存储所有点的val
int dep[maxn]; // 存储所有点的高度
int a[maxn]; // 存储边的左边顶点
int b[maxn]; // 存储边的右边顶点
int n, q; // 顶点数,操作次数
// 求x结点的孩子的高度,记到dep中,fa是x的父节点
void getDepth(int x, int fa){
for(int v : g[x]){
if(v == fa) continue; //跳过父节点
dep[v] = dep[x] + 1;
getDepth(v, x); // 继续遍历计算孩子的孩子的高度
}
}
// 汇总所有和,计算最终结果
void getAns(int x, int fa){
for(int v : g[x]){
if(v == fa) continue;
val[v] += val[x];
getAns(v, x);
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
cin >> n;
for(int i=1; i<n; i++){ //这里需要特别注意,n个点,n-1条边
// 输入并储存边
cin >> a[i] >> b[i];
g[a[i]].push_back(b[i]);
g[b[i]].push_back(a[i]);
}
getDepth(1, 1); // 1结点的高度是0,不用算,这里是计算它的所有孩子的高度
cin >> q;
while(q--){
int t, e, x; // 操作的点的编号,边的编号,要加的值
cin >> t >> e >> x;
int node1 = a[e], node2 = b[e];
if (t == 1) { // 经过a不经过b,对应node1
if(dep[node1] < dep[node2]){
// a部分加x,看成全部加x,然后b部分减x
val[node2] -= x;
val[1] += x; // 1结点加上x,相当于全局加x
} else {
val[node1] += x;
}
}
else if(t == 2){
if(dep[node2] < dep[node1]){
val[node1] -= x;
val[1] += x;
} else {
val[node2] += x;
}
}
}
getAns(1, 1); // 从1结点开始,计算所有结果
for(int i=1; i<=n; i++){
cout << val[i] << endl;
}
return 0;
}