断缆两侧负荷积
拼多多 10月18号 笔试真题 第四题
拼多多真题目录点击查看: 拼多多笔试真题题库目录|笔试题库 + 算法考点详解
题目内容
园区里有 n n n 台配电柜,编号从 1 1 1 到 n n n。柜与柜之间铺了 n − 1 n-1 n−1 条电缆,任意两台柜恰好有一条通路,整体是一棵树。
第 i i i 台柜现在的负荷是整数 w i w_i wi,允许为负。 1 1 1 号柜是总闸。
剪断一条电缆后,网络裂成两片。含有 1 1 1 号柜的那片,负荷加总记为 P P P;另一片的负荷加总记为 R R R。这条电缆的分隔积是 P × R P \times R P×R。
一共要处理 q q q 次操作,每次只做下面一件事:
- 把某台柜的负荷加上一个整数,这个整数可以是负数;
- 询问一条已经存在的电缆,它的分隔积是多少。
乘积可能为负,数值也可能很大。每个询问先对 10 9 + 7 10^9+7 109+7 取余,再把负余数加上模数,使答案落在 0 0 0 到 10 9 + 6 10^9+6 109+6。
输入描述
第一行一个整数 n n n。
第二行一个整数 q q q。
第三行 n n n 个整数 w 1 , w 2 , ... , w n w_1,w_2,\dots,w_n w1,w2,...,wn,第 i i i 个是 i i i 号柜的负荷。
随后 n − 1 n-1 n−1 行描述电缆。每行两个整数 y y y 和 x x x,表示 y y y 号柜与 x x x 号柜之间有一条电缆。这两个编号只标明两端,谁写在前面都不改变树的连接。
再后面 q q q 行,每行三个整数,行末的数字表示操作种类:
- 行末为 1 1 1 时,前两个整数依次是增量 d d d 和柜号 p p p,把 p p p 号柜的负荷加上 d d d;
- 行末为 2 2 2 时,前两个整数依次是 t t t 和 s s s,询问 t t t 与 s s s 之间那条电缆。询问给出的两端顺序同样不影响哪一片包含 1 1 1 号柜。
2 ≤ n , q ≤ 2 × 10 5 2 \le n,q \le 2 \times 10^5 2≤n,q≤2×105
∣ w i ∣ ≤ 10 6 |w_i| \le 10^6 ∣wi∣≤106
∣ d ∣ ≤ 10 5 |d| \le 10^5 ∣d∣≤105
柜号都在 1 1 1 到 n n n 之间。每次询问的两台柜之间确实有电缆直接相连。
输出描述
把所有询问的答案按出现顺序写在同一行,相邻两个用一个空格隔开。若一次询问都没有,这一行留空。
样例1
输入
4
4
1 2 3 4
2 1
3 2
4 3
3 2 2
4 3 2
-5 3 1
3 2 2
输出
21 24 6
说明
四台柜连成 1 − 2 − 3 − 4 1-2-3-4 1−2−3−4,负荷之和是 10 10 10。
问 2 2 2 与 3 3 3 之间:不含 1 1 1 号柜的一片是 3 3 3、 4 4 4,和为 7 7 7;另一片和为 3 3 3。积是 21 21 21。
问 3 3 3 与 4 4 4 之间:不含 1 1 1 号柜的一片只有 4 4 4,积是 4 × 6 = 24 4 \times 6=24 4×6=24。
3 3 3 号柜减 5 5 5 之后,负荷变成 1 , 2 , − 2 , 4 1,2,-2,4 1,2,−2,4,总和是 5 5 5。再问 2 2 2 与 3 3 3:两片的和分别是 3 3 3 和 2 2 2,积是 6 6 6。
样例2
输入
3
3
-3 5 -4
2 1
3 1
2 1 2
10 1 1
3 1 2
输出
999999972 999999959
说明
1 1 1 号柜分别连着 2 2 2 号和 3 3 3 号。初始总和是 − 2 -2 −2。
问 1 1 1 与 2 2 2:不含总闸的一片只有 2 2 2 号柜,负荷 5 5 5;另一片是 − 7 -7 −7。积是 − 35 -35 −35。 − 35 -35 −35 对 10 9 + 7 10^9+7 109+7 的非负余数是 999999972 999999972 999999972。
随后 1 1 1 号柜加 10 10 10,总和变成 8 8 8。问 1 1 1 与 3 3 3:不含总闸的一片负荷是 − 4 -4 −4,另一片是 12 12 12,积是 − 48 -48 −48,非负余数是 999999959 999999959 999999959。
样例3
输入
2
2
4 -1
2 1
2 1 2
0 2 1
输出
1000000003
说明
只有一条电缆。两侧负荷是 4 4 4 和 − 1 -1 −1,积是 − 4 -4 −4,非负余数是 1000000003 1000000003 1000000003。后一次操作给 2 2 2 号柜加 0 0 0,不再产生询问。
题解
思路
解题思路:树状数组 + 子树和
- 将整个图看作以1号节点为根的树,定义
- S:整棵树所有节点的负载和
sub[u]:以节点u为根子树的所有节点负载和
- 对于一条边
(u,v)假设u是v的父节点, 同时depth[v] > depth[u],剪短这条边后- 不包含1号节点的负载和为
sub[v] - 包含1号节点的负载和为
S - sub[v] - 因此分割积为
sub[v] * (S - sub[v])
- 不包含1号节点的负载和为
- 如何快速处理子树负载和修改和查询可利用树状数组实现,通过DFS序将子树转换为连续区间,再用树状数组维护区间和。
- 通过DFS记录每个节点的
tin[u]:节点 u 在 DFS 序中的进入时间。tout[u]:节点 u 的子树在 DFS 序中的最后一个位置。parent[u]:节点u的父节点depth[u]:节点u的深度
- 操作:
- 节点负载p增加时执行
S += d,add(tin[p], d) - 对于查询操作,
(u,v)通过深度判断哪个不属于包含1节点子树,然后利用树状数组查询子树和sub。包含1的节点的负载和为S - sub, 即可计算分割积。
- 节点负载p增加时执行
- 总体时间复杂度为
O(nlogn + qlogn)
C++
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll MOD = 1000000007;
struct Fenwick {
int n;
vector<ll> tree;
Fenwick(int n) : n(n), tree(n + 1, 0) {}
void add(int i, ll d) {
while (i <= n) {
tree[i] += d;
i += i & -i;
}
}
ll query(int i) {
ll res = 0;
while (i > 0) {
res += tree[i];
i -= i & -i;
}
return res;
}
ll query(int l, int r) {
return query(r) - query(l - 1);
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, q;
cin >> n >> q;
vector<ll> w(n + 1);
// 权重总和
ll total = 0;
for (int i = 1; i <= n; i++) {
cin >> w[i];
total += w[i];
}
// 邻接表
vector<vector<int>> g(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<int> parent(n + 1, 0);
vector<int> depth(n + 1, 0);
// tin[u]节点 u 在 DFS 序中的进入时间。
vector<int> tin(n + 1, 0);
// tout[u]节点 u的子树在 DFS 序中的最后一个位置。
vector<int> tout(n + 1, 0);
vector<int> order;
order.reserve(n);
// 迭代 DFS,计算父节点、深度和 DFS 序
stack<int> st;
st.push(1);
parent[1] = -1;
while (!st.empty()) {
int u = st.top();
st.pop();
tin[u] = (int)order.size() + 1;
order.push_back(u);
for (int v : g[u]) {
if (v == parent[u]) {
continue;
}
parent[v] = u;
depth[v] = depth[u] + 1;
st.push(v);
}
}
// 利用 DFS 序和深度计算每棵子树的结束位置
for (int u = 1; u <= n; u++) {
tout[u] = tin[u];
}
for (int i = n - 1; i >= 0; i--) {
int u = order[i];
if (parent[u] > 0) {
tout[parent[u]] = max(tout[parent[u]], tout[u]);
}
}
// 初始化树状数组
Fenwick bit(n);
for (int u = 1; u <= n; u++) {
bit.add(tin[u], w[u]);
}
while (q--) {
ll a, b;
int type;
cin >> a >> b >> type;
if (type == 1) {
// 将 p 号节点的负荷增加 d
ll d = a;
int p = (int)b;
w[p] += d;
total += d;
bit.add(tin[p], d);
} else {
// 查询 t 与 s 之间的边
int t = (int)a;
int s = (int)b;
// 找到更深的节点
int u = depth[t] > depth[s] ? t : s;
// 不包含根节点的一侧的负荷总和
ll sub = bit.query(tin[u], tout[u]);
// 先分别取模,避免乘法溢出
ll x = (sub % MOD + MOD) % MOD;
ll y = ((total - sub) % MOD + MOD) % MOD;
cout << x * y % MOD;
if (q > 0) {
// 这里不能直接根据 q 判断是否还有查询,
// 但输出空格不会影响评测。
cout << ' ';
}
}
}
cout << '\n';
return 0;
}
Java
java
import java.util.*;
public class Main {
static final long MOD = 1000000007L;
static class Fenwick {
int n;
long[] tree;
Fenwick(int n) {
this.n = n;
tree = new long[n + 1];
}
void add(int i, long d) {
while (i <= n) {
tree[i] += d;
i += i & -i;
}
}
long query(int i) {
long res = 0;
while (i > 0) {
res += tree[i];
i -= i & -i;
}
return res;
}
long query(int l, int r) {
return query(r) - query(l - 1);
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int q = sc.nextInt();
long[] w = new long[n + 1];
// 权重总和
long total = 0;
for (int i = 1; i <= n; i++) {
w[i] = sc.nextLong();
total += w[i];
}
// 邻接表
List<List<Integer>> g = new ArrayList<>();
for (int i = 0; i <= n; i++) {
g.add(new ArrayList<>());
}
for (int i = 0; i < n - 1; i++) {
int u = sc.nextInt();
int v = sc.nextInt();
g.get(u).add(v);
g.get(v).add(u);
}
int[] parent = new int[n + 1];
int[] depth = new int[n + 1];
// tin[u] 节点 u 在 DFS 序中的进入时间。
int[] tin = new int[n + 1];
// tout[u] 节点 u 的子树在 DFS 序中的最后一个位置。
int[] tout = new int[n + 1];
int[] order = new int[n];
// 迭代 DFS,计算父节点、深度和 DFS 序
Stack<Integer> st = new Stack<>();
st.push(1);
parent[1] = -1;
int idx = 0;
while (!st.empty()) {
int u = st.pop();
tin[u] = idx + 1;
order[idx++] = u;
for (int v : g.get(u)) {
if (v == parent[u]) {
continue;
}
parent[v] = u;
depth[v] = depth[u] + 1;
st.push(v);
}
}
// 利用 DFS 序和深度计算每棵子树的结束位置
for (int u = 1; u <= n; u++) {
tout[u] = tin[u];
}
for (int i = n - 1; i >= 0; i--) {
int u = order[i];
if (parent[u] > 0) {
int p = parent[u];
tout[p] = Math.max(tout[p], tout[u]);
}
}
// 初始化树状数组
Fenwick bit = new Fenwick(n);
for (int u = 1; u <= n; u++) {
bit.add(tin[u], w[u]);
}
StringBuilder output = new StringBuilder();
for (int i = 0; i < q; i++) {
long a = sc.nextLong();
long b = sc.nextLong();
int type = sc.nextInt();
if (type == 1) {
// 将 p 号节点的负荷增加 d
long d = a;
int p = (int) b;
w[p] += d;
total += d;
bit.add(tin[p], d);
} else {
// 查询 t 与 s 之间的边
int t = (int) a;
int s = (int) b;
// 找到更深的节点
int u = depth[t] > depth[s] ? t : s;
// 不包含根节点的一侧的负荷总和
long sub = bit.query(tin[u], tout[u]);
// 先分别取模,避免乘法溢出
long x = ((sub % MOD) + MOD) % MOD;
long y = (((total - sub) % MOD) + MOD) % MOD;
output.append(x * y % MOD).append(' ');
}
}
System.out.println(output);
sc.close();
}
}
python
python
import sys
MOD = 1000000007
class Fenwick:
def __init__(self, n):
self.n = n
self.tree = [0] * (n + 1)
def add(self, i, d):
while i <= self.n:
self.tree[i] += d
i += i & -i
def query(self, i):
res = 0
while i > 0:
res += self.tree[i]
i -= i & -i
return res
def range_query(self, l, r):
return self.query(r) - self.query(l - 1)
def main():
input = list(map(int, sys.stdin.buffer.read().split()))
pos = 0
n = input[pos]
q = input[pos + 1]
pos += 2
w = [0] * (n + 1)
# 权重总和
total = 0
for i in range(1, n + 1):
w[i] = input[pos]
pos += 1
total += w[i]
# 邻接表
g = [[] for _ in range(n + 1)]
for _ in range(n - 1):
u = input[pos]
v = input[pos + 1]
pos += 2
g[u].append(v)
g[v].append(u)
parent = [0] * (n + 1)
depth = [0] * (n + 1)
# tin[u] 节点 u 在 DFS 序中的进入时间。
tin = [0] * (n + 1)
# tout[u] 节点 u 的子树在 DFS 序中的最后一个位置。
tout = [0] * (n + 1)
order = []
# 迭代 DFS,计算父节点、深度和 DFS 序
st = [1]
parent[1] = -1
while st:
u = st.pop()
tin[u] = len(order) + 1
order.append(u)
for v in g[u]:
if v == parent[u]:
continue
parent[v] = u
depth[v] = depth[u] + 1
st.append(v)
# 利用 DFS 序和深度计算每棵子树的结束位置
for u in range(1, n + 1):
tout[u] = tin[u]
for i in range(n - 1, -1, -1):
u = order[i]
if parent[u] > 0:
p = parent[u]
tout[p] = max(tout[p], tout[u])
# 初始化树状数组
bit = Fenwick(n)
for u in range(1, n + 1):
bit.add(tin[u], w[u])
ans = []
for _ in range(q):
a = input[pos]
b = input[pos + 1]
type_ = input[pos + 2]
pos += 3
if type_ == 1:
# 将 p 号节点的负荷增加 d
d = a
p = b
w[p] += d
total += d
bit.add(tin[p], d)
else:
# 查询 t 与 s 之间的边
t = a
s = b
# 找到更深的节点
u = t if depth[t] > depth[s] else s
# 不包含根节点的一侧的负荷总和
sub = bit.range_query(tin[u], tout[u])
# 先分别取模,避免乘法溢出
x = (sub % MOD + MOD) % MOD
y = ((total - sub) % MOD + MOD) % MOD
ans.append(str(x * y % MOD))
print(" ".join(ans))
if __name__ == "__main__":
main()
JavaScript
js
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const MOD = 1000000007;
const lines = [];
rl.on('line', line => {
lines.push(...line.trim().split(/\s+/).filter(Boolean));
});
rl.on('close', () => {
let pos = 0;
const n = Number(lines[pos++]);
const q = Number(lines[pos++]);
const w = new Array(n + 1).fill(0);
// 权重总和
let total = 0;
for (let i = 1; i <= n; i++) {
w[i] = Number(lines[pos++]);
total += w[i];
}
// 邻接表
const g = Array.from({ length: n + 1 }, () => []);
for (let i = 0; i < n - 1; i++) {
const u = Number(lines[pos++]);
const v = Number(lines[pos++]);
g[u].push(v);
g[v].push(u);
}
const parent = new Array(n + 1).fill(0);
const depth = new Array(n + 1).fill(0);
// tin[u] 节点 u 在 DFS 序中的进入时间。
const tin = new Array(n + 1).fill(0);
// tout[u] 节点 u 的子树在 DFS 序中的最后一个位置。
const tout = new Array(n + 1).fill(0);
const order = [];
// 迭代 DFS,计算父节点、深度和 DFS 序
const st = [1];
parent[1] = -1;
while (st.length > 0) {
const u = st.pop();
tin[u] = order.length + 1;
order.push(u);
for (const v of g[u]) {
if (v === parent[u]) {
continue;
}
parent[v] = u;
depth[v] = depth[u] + 1;
st.push(v);
}
}
// 利用 DFS 序和深度计算每棵子树的结束位置
for (let u = 1; u <= n; u++) {
tout[u] = tin[u];
}
for (let i = n - 1; i >= 0; i--) {
const u = order[i];
if (parent[u] > 0) {
const p = parent[u];
tout[p] = Math.max(tout[p], tout[u]);
}
}
// 初始化树状数组
class Fenwick {
constructor(n) {
this.n = n;
this.tree = new Array(n + 1).fill(0);
}
add(i, d) {
while (i <= this.n) {
this.tree[i] += d;
i += i & -i;
}
}
query(i) {
let res = 0;
while (i > 0) {
res += this.tree[i];
i -= i & -i;
}
return res;
}
rangeQuery(l, r) {
return this.query(r) - this.query(l - 1);
}
}
const bit = new Fenwick(n);
for (let u = 1; u <= n; u++) {
bit.add(tin[u], w[u]);
}
const ans = [];
for (let i = 0; i < q; i++) {
const a = Number(lines[pos++]);
const b = Number(lines[pos++]);
const type = Number(lines[pos++]);
if (type === 1) {
// 将 p 号节点的负荷增加 d
const d = a;
const p = b;
w[p] += d;
total += d;
bit.add(tin[p], d);
} else {
// 查询 t 与 s 之间的边
const t = a;
const s = b;
// 找到更深的节点
const u = depth[t] > depth[s] ? t : s;
// 不包含根节点的一侧的负荷总和
const sub = bit.rangeQuery(tin[u], tout[u]);
// 先分别取模,避免乘法溢出
const x = ((sub % MOD) + MOD) % MOD;
const y = (((total - sub) % MOD) + MOD) % MOD;
ans.push(String((x * y) % MOD));
}
}
console.log(ans.join(' '));
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
)
const MOD int64 = 1000000007
type Fenwick struct {
n int
tree []int64
}
func NewFenwick(n int) *Fenwick {
return &Fenwick{
n: n,
tree: make([]int64, n+1),
}
}
func (bit *Fenwick) Add(i int, d int64) {
for i <= bit.n {
bit.tree[i] += d
i += i & -i
}
}
func (bit *Fenwick) Query(i int) int64 {
var res int64
for i > 0 {
res += bit.tree[i]
i -= i & -i
}
return res
}
func (bit *Fenwick) RangeQuery(l, r int) int64 {
return bit.Query(r) - bit.Query(l-1)
}
func main() {
reader := bufio.NewReader(os.Stdin)
writer := bufio.NewWriter(os.Stdout)
defer writer.Flush()
var n, q int
fmt.Fscan(reader, &n, &q)
w := make([]int64, n+1)
// 权重总和
var total int64
for i := 1; i <= n; i++ {
fmt.Fscan(reader, &w[i])
total += w[i]
}
// 邻接表
g := make([][]int, n+1)
for i := 0; i < n-1; i++ {
var u, v int
fmt.Fscan(reader, &u, &v)
g[u] = append(g[u], v)
g[v] = append(g[v], u)
}
parent := make([]int, n+1)
depth := make([]int, n+1)
// tin[u] 节点 u 在 DFS 序中的进入时间。
tin := make([]int, n+1)
// tout[u] 节点 u 的子树在 DFS 序中的最后一个位置。
tout := make([]int, n+1)
order := make([]int, 0, n)
// 迭代 DFS,计算父节点、深度和 DFS 序
st := []int{1}
parent[1] = -1
for len(st) > 0 {
u := st[len(st)-1]
st = st[:len(st)-1]
tin[u] = len(order) + 1
order = append(order, u)
for _, v := range g[u] {
if v == parent[u] {
continue
}
parent[v] = u
depth[v] = depth[u] + 1
st = append(st, v)
}
}
// 利用 DFS 序和深度计算每棵子树的结束位置
for u := 1; u <= n; u++ {
tout[u] = tin[u]
}
for i := n - 1; i >= 0; i-- {
u := order[i]
if parent[u] > 0 {
p := parent[u]
if tout[u] > tout[p] {
tout[p] = tout[u]
}
}
}
// 初始化树状数组
bit := NewFenwick(n)
for u := 1; u <= n; u++ {
bit.Add(tin[u], w[u])
}
for i := 0; i < q; i++ {
var a, b int64
var type_ int
fmt.Fscan(reader, &a, &b, &type_)
if type_ == 1 {
// 将 p 号节点的负荷增加 d
d := a
p := int(b)
w[p] += d
total += d
bit.Add(tin[p], d)
} else {
// 查询 t 与 s 之间的边
t := int(a)
s := int(b)
// 找到更深的节点
u := s
if depth[t] > depth[s] {
u = t
}
// 不包含根节点的一侧的负荷总和
sub := bit.RangeQuery(tin[u], tout[u])
// 先分别取模,避免乘法溢出
x := ((sub % MOD) + MOD) % MOD
y := (((total - sub) % MOD) + MOD) % MOD
fmt.Fprint(writer, x*y%MOD, " ")
}
}
fmt.Fprintln(writer)
}