题目链接:https://ac.nowcoder.com/acm/contest/6234/H
题目大意:有一棵n节点的树,每个节点上有权值,希望你把他的k-1条边拆开。形成k棵子树,每棵树的权值为节点权值的和。问这k棵子树的最大权值最小为多少?
思路:二分,然后我们思考,对于一个节点,他会和若干子节点连接,并且可能和祖先节点连接
思路:先二分一下,这个最小值。然后在树上能不能分成为<=k棵子树。我们怎么贪心的分树:
对这个树,我们先处理2,5,6,7。一定是2和若干的子节点在一起,那么其他的每个子节点都会形成一颗树。我们希望2上连接的子节点越多越好并且权力和<=mid。那么对5, 6, 7的权值排序。一直加直到>mid。就不能加,那么其他的每个节点形成一颗子树。对于2连接的子树的权值和。应该作为2的权值。用于1的贪心。对3,4子树处理完后:
对1的贪心思路和2的贪心思路一样。
时间复杂度:O(nlogn):因为每个节点只会排序一次。
#include<bits/stdc++.h> #define LL long long using namespace std; vector<vector<LL> > v(100005); vector<LL> g[100005]; LL w[100005], ANS=0; LL DFS(LL u, LL fa, LL x){ LL s=w[u]; for(auto to: v[u]){ if(to==fa) continue; g[u].push_back(DFS(to, u, x)); } sort(g[u].begin(), g[u].end()); for(int i=0; i<g[u].size(); i++){ if(s+g[u][i]>x){ ANS+=g[u].size()-i; break; } else{ s+=g[u][i]; } } g[u].clear(); return s; } bool check(LL x, LL k){ ANS=1; DFS(1, 0, x); return ANS<=k; } int main(){ int T, cas=1, x; scanf("%d", &T); while(T--){ LL n, k, x, y; scanf("%lld%lld", &n, &k); for(int i=1; i<n; i++){ scanf("%lld%lld", &x, &y); v[x].push_back(y); v[y].push_back(x); } LL l=0, r=0, ans=0; for(int i=1; i<=n; i++){ scanf("%lld", &w[i]); l=max(l, w[i]); r+=w[i]; } while(l<=r){ LL mid=l+r>>1; if(check(mid, k)){ r=mid-1, ans=mid; } else{ l=mid+1; } } for(int i=1; i<=n; i++){ v[i].clear(); } printf("Case #%d: %lld\n", cas++, ans); } return 0; }