保卫王国

题目描述

Z 国有 $n$ 座城市,$n-1$条双向道路,每条双向道路连接两座城市,且任意两座城市 都能通过若干条道路相互到达。

Z 国的国防部长小 Z 要在城市中驻扎军队。驻扎军队需要满足如下几个条件:

一座城市可以驻扎一支军队,也可以不驻扎军队。
由道路直接连接的两座城市中至少要有一座城市驻扎军队。
在城市里驻扎军队会产生花费,在编号为$i$的城市中驻扎军队的花费是$p_i$。小 Z 很快就规划出了一种驻扎军队的方案,使总花费最小。但是国王又给小 Z 提出 了$m$个要求,每个要求规定了其中两座城市是否驻扎军队。小 Z 需要针对每个要求逐一 给出回答。具体而言,如果国王提出的第$j$个要求能够满足上述驻扎条件(不需要考虑 第 $j$ 个要求之外的其它要求),则需要给出在此要求前提下驻扎军队的最小开销。如果 国王提出的第$j$个要求无法满足,则需要输出$-1 (1 ≤ j ≤ m)$。现在请你来帮助小 Z。

第 1 行包含两个正整数$n,m$和一个字符串$type$,分别表示城市数、要求数和数据类型。$type$是一个由大写字母 $A,B$ 或 $C$ 和一个数字 $1,2,3$ 组成的字符串。它可以帮助你获得部分分。你可能不需要用到这个参数。这个参数的含义在【数据规模与约定】中 有具体的描述。然而我并没有放
第 2 行$n$个整数$p_i$,表示编号$i$的城市中驻扎军队的花费。
接下来 $n−1$ 行,每行两个正整数$u,v$,表示有一条$u$到$v$的双向道路。
接下来 $m$ 行,第$j$行四个整数$a,x,b,y(a≠b)$,表示第$j$个要求是在城市$a$驻扎$x$支军队, 在城市$b$驻扎$y$支军队。其中,$x , y$的取值只有 $0$ 或 $1$:若 $x$ 为 $0$,表示城市 $a$ 不得驻扎军队,若 $x$ 为 $1$,表示城市 $a$ 必须驻扎军队;$y$ 同理.
输出共 $m$ 行,每行包含 $1$ 个整数,第$j$行表示在满足国王第$j$个要求时的最小开销, 如果无法满足国王的第$j$个要求,则该行输出 $-1$。

$n,m \le 100000,1 \le p_i \le 100000$

代码

这个是真的口胡不出…
csy dalao题解
还是讲的挺明白易懂的
大概复制总结了一下

数组定义
$f[i][0/1]$:选或不选$i$结点,选择以$i$为根的子树所有结点的最优值
$g[i][0/1]$:选或不选$i$结点,整棵树除了以$i$为根的子树以外全部选择的最优值

g[i][0/1]不包含i节点的贡献,但它是在i节点确定了是否选择的前提下计算出来的

$fa[i][j]$:结点i的$2^j$祖先
$dp[i][j][0/1][0/1]$:$i$结点选或不选,$fa[i][j]$选或不选,以$fa$为根的子树内独不选以$i$为根的子树的最优值
相当于是 $i$ 结点相对于它的 $2^j$ 祖先结点为根的树的另一个 $g$ 数组
倍增处理

ps:dp[i][j][0/1][0/1]在右下角出锅了…不应该包括i的左子树.致歉

处理出倍增数组之后,我们对于两个询问的点不断向上跳并统计dp值,如果其中一个是另一个的祖先结点,那么这一个的答案就可以直接输出。否则继续向上统计答案,直到到达lca处。
因为被修改的两点的影响范围是有限的,对于范围之外的点它们的dp值并不会受到影响
如果修改的两点是相连的并且修改值都为 0 ,则输出$-1$

向上跳:对于当前点$x$来说,记录它的子树最优值$tx[1/0]$,代表当前点选或不选的子树最优值。那么对于初始修改点,$tx$数组的两个数里只会有一个是有值的,另一个设为$inf$即可。点$y$同理

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
//函数①:预处理dep数组, 简单处理fa数组
void dep_fa(int node){
for(int i = head[node]; i; i = ne[i]){
int To = to[i];
if(To == fa[node][0]) continue;
fa[To][0] = node;
dep[To] = dep[node] + 1;
dep_fa(To);
}
}

//函数②:预处理f数组
void get_f(int node){
f[node][1] = cost[node];//很好理解
for(int i = head[node]; i; i = ne[i]){
int To = to[i];
if(To == fa[node][0]) continue;
get_f(To);
f[node][1] += min(f[To][0], f[To][1]);//注意这里对f[node][1]的状态转移!!
f[node][0] += f[To][1];
}
}

//函数③:预处理g数组
void get_g(int node){
for(int i = head[node]; i; i = ne[i]){
int To = to[i];
if(To == fa[node][0]) continue;
g[To][0] = g[node][1] + f[node][1] - min(f[To][0], f[To][1]);
//减掉To及它的子树对f[node][1]曾经产生过的贡献
//因为之前做出的贡献为min(f[To][0], f[To][1]),所以减掉也是相同的量
g[To][1] = min(g[To][0], g[node][0] + f[node][0] - f[To][1]);
get_g(To);
}
}

//函数④:预处理dp数组
void get_dp(){
for(int i = 1; i <= n; ++ i){
dp[i][0][0][0] = MAXN;
dp[i][0][1][0] = f[fa[i][0]][0] - f[i][1];//因为有直接边相连啊
dp[i][0][0][1] = dp[i][0][1][1] = f[fa[i][0]][1] - min(f[i][0], f[i][1]);
//同样按照之前的减去贡献原则理解
}
for(int i = 1; i <= 19; ++ i){
for(int j = 1; j <= n; ++ j){
fa[j][i] = fa[fa[j][i-1]][i-1];//别漏了
for(int x = 0; x <= 1; ++ x)//枚举j结点状态
for(int y = 0; y <= 1; ++ y){//枚举fa[j][i]结点状态
dp[j][i][x][y] = MAXN;//这里要记得初始化**
for(int k = 0; k <= 1; ++ k)//枚举中间结点的状态,取最优值
dp[j][i][x][y] = min(dp[j][i][x][y],
dp[j][i-1][x][k] + dp[fa[j][i-1]][i-1][k][y]);
}
}
}
}

//核心!!!
LL get_ans(){
if(!akey && !bkey && ((fa[a][0] == b) || (fa[b][0] == a))) return -1;

if(dep[a] > dep[b]) swap(a, b), swap(akey, bkey);
//令dep[b] >= dep[a], 跳b
//akey和bkey也要记得交换啊
pa[akey ^ 1] = pb[bkey ^ 1] = MAXN;//初始化
pa[akey] = f[a][akey], pb[bkey] = f[b][bkey];

for(int i = 19; i >= 0; -- i){
if(dep[fa[b][i]] >= dep[a]){
nb[0] = nb[1] = MAXN;
for(int x = 0; x <= 1; ++ x)
for(int y = 0; y <= 1; ++ y)
nb[x] = min(nb[x], dp[b][i][y][x] + pb[y]);
//nowb结点的状态由preb结点的状态转移而来
b = fa[b][i];//要在使用过后更新...
pb[0] = nb[0], pb[1] = nb[1];
}
}
if(b == a) return nb[akey] + g[a][akey];
//注意理解?
//同样谨记g数组不包括a结点的贡献

for(int i = 19; i >= 0; -- i){
if(fa[a][i] != fa[b][i]){
na[0] = na[1] = nb[0] = nb[1] = MAXN;
for(int x = 0; x <= 1; ++ x)
for(int y = 0; y <= 1; ++ y){
nb[x] = min(nb[x], dp[b][i][y][x] + pb[y]);
na[x] = min(na[x], dp[a][i][y][x] + pa[y]);
}
a = fa[a][i], b = fa[b][i];
pa[0] = na[0], pa[1] = na[1];
pb[0] = nb[0], pb[1] = nb[1];
}
}
int lca = fa[a][0];
return min(g[lca][0] + pa[1] + pb[1] + f[lct][0] - f[a][1] - f[b][1],
g[lca][1] + f[lca][1] + min(pa[1], pa[0]) + min(pb[1], pb[0])
- min(f[a][1], f[a][0]) - min(f[b][1], f[b][0]));
//在lca的两种状态之间取min作为答案
}

int main(){
n = read(), m = read(), cin >> c, type = read();
for(int i = 1; i <= n; ++ i) cost[i] = read();
for(int i = 1, x, y; i < n; ++ i) { add((x = read()), (y = read())), add(y, x);}
dep[1] = 1, dep_fa(1);
get_f(1);
get_g(1);
get_dp();
for(int i = 1; i <= m; ++ i){
a = read(), akey = read(), b = read(), bkey = read();
printf("%lld\n", get_ans());
}
return 0;
}

日常

1.当时csy dalao教我这道题教了一个晚自习
真的真的非常感谢

2.吐槽一下不明白什么样的神仙才能在考场上把这份代码码完并且调试好
%一波

3.后天元旦晚会啦
有点累但是好高兴啊

4.想学DDP咕咕咕