题目大意:给一棵N个结点的树,将它分成K个连通块,设ki为每个连通块的权值和,令gi为所有ki中的最小值,求gi的最大值。
思路历程就不讲了,写了一个优先级队列去贪心,wa了9发才去找反例推出自己的贪心是错解
显然分成的连通块越少,每个连通块的权值越大,那么其中权值和最小的块的权值也越大(有点绕,好好读一读),这意味着答案具有单调性,虽然题目说K是固定的,我们可以二分判断是否满足要求
具体做法:二分枚举每个块的最低权值,贪心得搜索,使得拼成的权值符合要求的连通块尽可能的多,可以用Dfs求出连通块数,然后二分即可。
具体过程:每次对最低权值mid进行dfs搜索判断连通块的数量cnt,这里用二分来找第一个不满足条件的mid(即upper_bound),它的上一个值是最后一个满足条件的值(也就是我们要求的值),如果cnt >=k ,令l = mid + 1,并且令ans = mid(保存上一次的解),否则 r = mid。
至于如何贪心的搜索:从叶子结点开始,考虑把每个结点的子节点和它连接在一起构成一个连通块使得权值大于等于我们搜索的权值,如果子节点所在连通块权值小于 mid,那么我们将它合并到当前结点,并更新一下当前连通块的权值,如果子节点所在连通块权值大于等于 mid,那么我们不将它加入到当前点的连通块,因为子节点所在一个连通块已经是一个独立的连通块,即使我们把这个连通块加进来,也不会使答案变得更大,并且失去了一个本来可以和其他结点拼凑出另外一个满足条件的连通块的结点,而考虑不把它加进来,我们还可以用当前结点和其他点拼起来凑出新的答案,显然这样决策每一步都是最优的。
写法上,dfs搜索就行,而不需要用到什么并查集。
#include<bits/stdc++.h>
using namespace std;
int n,k,x,y;
const int maxn = 1e5 + 10;
vector<int> g[maxn];
int a[maxn];
long long dfs(int u,int fa,long long v,int &cnt){
long long tmp = a[u];
for(int i = 0; i < g[u].size(); i++){
int vi = g[u][i];
if(vi == fa) continue;
tmp += dfs(vi,u,v,cnt);
}
if(tmp >= v){
cnt++;
return 0;
}
else return tmp;
}
long long search(){
int cnt = 0;
long long l = 1,r = 1e10+1;
long long ans = l;
while(l < r){
cnt = 0;
long long mid = l + r >> 1;
dfs(1,-1,mid,cnt);
if(cnt < k){
r = mid;
}
else {
ans = mid;
l = mid + 1;
}
}
return ans;
}
int main(){
scanf("%d%d",&n,&k);
for(int i = 1; i < n; i++){
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
for(int i = 1; i <= n; i++)
scanf("%d",&a[i]);
printf("%lld\n",search());
return 0;
}