Timi / 提米树
题目链接:jzoj 5336
题目大意
给你一棵树,树上点有权值。
然后树的分数是它们叶子节点权值和减去所有相邻叶子的代价。
相邻叶子是两个叶子节点,它们在 dfs 序之间没有别的叶子节点。代价则是它们之间的路径上(不包含这两个叶子节点)最大的点权值。
然后你可以对树进行若干次剪枝,剪枝是如果一个点的所有儿子都是叶子节点,你就可以把它的所有儿子删去。
要你给出能有的最大分数。
思路
首先我们来看一些性质:
它可以无限次操作剪枝,所以你可以相当于对一个点剪枝就是把它的儿子部分全部减掉。
然后不难发现你要让两个点成为相邻的叶子,一定要它们在原来树的某两个相邻叶子的路径上。
然后我们不难想出
O
(
n
2
)
O(n^2)
O(n2) 的暴力。
f
i
f_i
fi 为 dfs 序为
1
∼
i
1\sim i
1∼i 的处理好的最优分数。
然后不难想到枚举叶子,然后枚举叶子之间的路径,然后路径两边各自枚举,然后转移。
然后考虑优化,我们把转移的式子列出来:
f
i
=
max
(
f
j
−
max
(
g
i
,
g
j
)
)
+
a
i
f_i=\max(f_j-\max(g_i,g_j))+a_i
fi=max(fj−max(gi,gj))+ai
(
g
i
g_i
gi 是
i
i
i 到 LCA 的路径中权值最大的点权,
a
i
a_i
ai 则是点权)
不难想到可以预处理出
g
g
g,以及
f
−
g
f-g
f−g
显然
g
g
g 有单调性,可以把
g
g
g 分成
g
i
>
g
j
g_i>g_j
gi>gj 和
g
i
⩽
g
j
g_i\leqslant g_j
gi⩽gj 两种。(
i
,
j
i,j
i,j 分别代表左右两边的点)
那就枚举右边,找左边的分界点就搞个指针就可以了。
代码
#include<cstdio>
#include<vector>
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
int n, fa[100001], deg[100001];
int a[100001], x, y, dfn[100001];
int Leaf[100001], tmp, f[100001];
int X[100001], Y[100001], s[100002];
int g[100002], left, right, num, ans;
vector <int> son[100001];
void dfs(int now) {
dfn[now] = ++tmp;
if (!son[now].size()) Leaf[++Leaf[0]] = now;
for (int i = 0; i < son[now].size(); i++) {
deg[son[now][i]] = deg[now] + 1;
dfs(son[now][i]);
}
}
void get_first() {
x = Leaf[1];
while (x != 1) {
f[x] = a[x];
x = fa[x];
}
a[x] = a[x];
}
void get_road() {
X[0] = Y[0] = 0;
int xx = x, yy = y;
while (deg[xx] > deg[yy]) {
X[++X[0]] = xx;
xx = fa[xx];
}
while (deg[yy] > deg[xx]) {
Y[++Y[0]] = yy;
yy = fa[yy];
}
while (xx != yy) {
X[++X[0]] = xx;
Y[++Y[0]] = yy;
xx = fa[xx]; yy = fa[yy];
}
}
void get_ans() {
x = Leaf[Leaf[0]];
while (x != 1) {
ans = max(ans, f[x]);
x = fa[x];
}
ans = max(ans, f[x]);
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
scanf("%d", &x);
for (int j = 1; j <= x; j++) {
scanf("%d", &y);
son[i].push_back(y);
fa[y] = i;
}
}
deg[1] = 1;
dfs(1);
get_first();
for (int i = 2; i <= Leaf[0]; i++) {
x = Leaf[i - 1]; y = Leaf[i];
get_road();
s[X[0] + 1] = 0;
for (int i = X[0]; i >= 0; i--)//先跑出后缀
s[i] = max(s[i + 1], a[fa[X[i]]]);
g[0] = -INF;
for (int i = 1; i <= X[0]; i++)//跑出原来路径只有左边的部分
g[i] = max(g[i - 1], f[X[i]] - s[i]);
left = right = -INF; num = X[0];
for (int i = Y[0]; i >= 1; i--) {
right = max(right, a[fa[Y[i]]]);
while (num && right > s[num]) {//找到分界点
left = max(left, f[X[num]]);
num--;
}
f[Y[i]] = max(left - right, g[num]) + a[Y[i]];//左边和右边选优的那个
}
}
get_ans();
printf("%d", ans);
return 0;
}