题意:给定 n 个结点的树,结点带权,给定一个数 k,要求把这棵树分成 k 个部分,使得每部分的结点权值和中的最大值最小,求该最小值。
解法:要使得最大值最小,所以需要二分枚举最大值lim,问题变成判断是否能划分出k-1个连通块使得最大值不超过lim,即判定划分连通块权值不超过lim的个数是否小于等于k,即割去的边数小于k。
再对每一次check内部的树上dp来看,我们对于u作为根节点分析。他会有许多子节点v[i],我们对v[i]进行以[i]从小到大排序。看此时dp[u]能添加最多几个子节点dp[v]使得满足其≤limit,满足的话res就不变;超出的话就加其他没有添加进去的子节点的数量即可。
总结:想法还是很中规中矩,感觉不太难。但是一时半会没想出来,可能是对于树上dp问题还是不太熟练造成的。
#include<bits/stdc++.h> #pragma GCC optimize(2) #define ll long long #define rep(i,a,n) for(int i=a;i<=n;i++) #define per(i,n,a) for(int i=n;i>=a;i--) #define endl ' ' #define eps 0.000000001 #define pb push_back #define mem(a,b) memset(a,b,sizeof(a)) #define IO ios::sync_with_stdio(false);cin.tie(0); using namespace std; const int INF=0x3f3f3f3f; const ll inf=0x3f3f3f3f3f3f3f3f; const int mod=1e9+7; const int maxn=2e5+5; ll val[maxn],dp[maxn]; vector<int> G[maxn]; int n,k; int dfs(int u,int fa,ll limit){ vector<ll> sondp; dp[u]=val[u]; int res=0; for(auto v:G[u]){ if(v==fa) continue; res+=dfs(v,u,limit); sondp.pb(dp[v]); } sort(sondp.begin(),sondp.end()); for(int i=0;i<sondp.size();i++){ if(dp[u]+sondp[i]<=limit){ dp[u]+=sondp[i]; }else{ res+=sondp.size()-i; break; } } return res; } bool check(ll lim){ return dfs(1,-1,lim)<k; } int main(){ int T;scanf("%d",&T); rep(cas,1,T){ scanf("%d%d",&n,&k); rep(i,1,n-1){ int u,v;scanf("%d%d",&u,&v); G[u].push_back(v);G[v].push_back(u); } ll sum=0,mx=0; rep(i,1,n){ scanf("%lld",&val[i]); sum+=val[i]; mx=max(mx,val[i]); } ll l=mx,r=sum; while(l<r){ ll mid=(l+r)>>1; if(check(mid)) r=mid; else l=mid+1; } printf("Case #%d: %lld ",cas,l); rep(i,1,n) G[i].clear(),dp[i]=0,val[i]=0; } }