题目大意
给你一棵树,树上的节点有白点有黑点,其中找到一条路径使得在经过的黑色点数不超过K的时候 路径长度最大,输出这个最大值
input
8 2 3 (n, k, m)
3
5
7 (m个黑点)
1 3 1
2 3 10
3 4 -2
4 5 -1
5 7 6
5 6 5
4 8 3 (边的连接情况以及权值)
output
12
idea
我做这道题做了好久啊。
这道题的核心就是怎么维护一条路上黑点个数在0~x范围内的路径最大值。我一开始没有想好,用的二分,后来wa了,仔细想了想,这个维护不了。又用了线段树。查询只需要logn,但是递归完一个子树的时候需要把线段树重新初始化,这样复杂度又变成了n2。
看了别人的代码,当时做这个题的时候也是看了很久很久,看得自己也是稀里糊涂的,昨天晚上整理,去看了一下启发式合并,虽然那个题没看懂,但是听他们一说,大约明白了启发式合并的意思,就是把小的集合往大的集合合并。
比如[1, 2], [1,2,3,4,5,6,7…1e9] 我们要合并这两个集合,如果把第二个集合合并到第一个集合,复杂度很大,所以要把第一个集合合并到第二个集合。
这里就很巧妙地用到了这个思想。
当我们跑到以root为根的另一个子树的时候,对于正在跑的该点,需要找已经跑过的子树符合条件的并且路径最大的(比如黑点的个数不能超过10,跑到当前点黑点的个数已经有3个了,我们就要去之前跑过的子树里找黑点个数在0~7范围内的路径的最大值),然后想了很多办法发现都不行,只能暴力去跑。然后就知道了要用启发式合并。先把每棵子树的黑点个数的最大值跑出来,排个序,每次用在黑点个数少的里面去找合法的。
然后时间复杂度是nlogn
code
#include <bits/stdc++.h>
using namespace std;
#define lson Root << 1
#define rson Root << 1 | 1
#define MID int mid = (l + r) / 2
const int maxn = 500000;
const int INF = 0x3f3f3f3f;
struct node {
int v, w;
};
int k, allNode, sonMax[maxn], sonNum[maxn], ans, root, n;
bool vis[maxn], black[maxn];
vector<node> Edge[maxn];
void getroot(int now, int pre) {
sonNum[now] = 1;
sonMax[now] = 0;
for(int i = 0; i < Edge[now].size(); i++) {
int to = Edge[now][i].v;
if(to == pre || vis[to]) continue;
getroot(to, now);
sonNum[now] += sonNum[to];
sonMax[now] = max(sonMax[now], sonNum[to]);
}
sonMax[now] = max(sonMax[now], allNode - sonNum[now]);
if(sonMax[now] < sonMax[root]) root = now;
}
int blackNum[maxn], dist[maxn], Max_Black;
//blackNum[i] 是i点的黑点个数
//dist[i] 是i点到根的距离
//Max_Black 是该子树黑点的最大值
void Get_Max_Black(int now, int pre) { //求该子树黑点的最大值
Max_Black = max(Max_Black, blackNum[now]);
for(int i = 0; i < Edge[now].size(); i++) {
int to = Edge[now][i].v;
if(vis[to] || to == pre) continue;
blackNum[to] = blackNum[now] + black[to];
dist[to] = dist[now] + Edge[now][i].w;
Get_Max_Black(to, now);
}
}
vector<pair<int, int> >sonTree; //first存黑点数最大值,second存是哪颗子树
int Max_dist[maxn]; //Max_dist[i] 是黑点数为i的距离最大值
void Get_dist(int now, int pre) { //得到Max_dist数组
int num = blackNum[now];
Max_dist[num] = max(Max_dist[num], dist[now]);
for(int i = 0; i < Edge[now].size(); i++) {
int to = Edge[now][i].v;
if(vis[to] || to == pre) continue;
Get_dist(to, now);
}
}
int Max[maxn];
void Deal_son_tree(int now) {
sonTree.clear();
for(int i = 0; i < Edge[now].size(); i++) {
int to = Edge[now][i].v;
if(vis[to]) continue;
Max_Black = 0;
dist[to] = Edge[now][i].w;
blackNum[to] = black[to];
Get_Max_Black(to, now); //求该子树黑点的最大值
sonTree.push_back(make_pair(Max_Black, to));
}
sort(sonTree.begin(), sonTree.end());
for(int i = 0; i < sonTree.size(); i++) { //启发式合并
Get_dist(sonTree[i].second, now);
int num = 0;
if(i) {
for(int j = sonTree[i].first; j >= 0; j--) {
while(num + j < k && num < sonTree[i - 1].first) {
num++;
Max[num] = max(Max[num], Max[num - 1]);
}
if(j + num <= k) ans = max(ans, Max[num] + Max_dist[j]);
}
}
if(i == sonTree.size() - 1) {
for(int j = 0; j <= sonTree[i].first; j++) {
if(j <= k) ans = max(max(Max[j], Max_dist[j]), ans);
Max[j] = Max_dist[j] = 0;
}
}
else {
for(int j = 0; j <= sonTree[i].first; j++) {
Max[j] = max(Max[j], Max_dist[j]);
Max_dist[j] = 0;
}
}
}
}
void solve(int now) {
vis[now] = true;
if(black[now]) k--;
Deal_son_tree(now);
if(black[now]) k++;
for(int i = 0; i < Edge[now].size(); i++) {
int to = Edge[now][i].v;
if(vis[to]) continue;
sonMax[0] = allNode = sonNum[to];
getroot(to, root = 0);
solve(root);
}
}
int main()
{
int m;
scanf("%d %d %d", &n, &k, &m);
for(int i = 0; i < m; i++) {
int u;
scanf("%d", &u);
black[u] = true;
}
for(int i = 1; i < n; i++) {
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
Edge[u].push_back((node){v, w});
Edge[v].push_back((node){u, w});
}
ans = 0;
sonMax[0] = allNode = n;
getroot(1, root = 0);
solve(root);
printf("%d\n", ans);
}