2019ICPC上海H-Tree Partition(二分+树上dp)

题意:给定 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 '\n'
#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\n",cas,l);
        rep(i,1,n) G[i].clear(),dp[i]=0,val[i]=0;
    }
}
View Code

 

posted @ 2021-03-27 02:09  Anonytt  阅读(90)  评论(0编辑  收藏  举报