Factories(Gym102222G)(树形dp+背包)

题意:

给你一棵由 \(n\) 个结点构成的树,树的每条边都有权值,问在树的叶子结点上选定 \(k\) 个结点,使任意两个结点之间的距离之和最短,求出最小值。


想法:

  • 结点之间距离之和最短问题,我们考虑每条边的贡献。此类问题大多数可以用树形dp解决。
  • \(dp[i][j]\) 表示编号为 \(i\) 的结点为根的子树中有 \(j\) 个叶子结点被使用时的最优解。
  • 考虑转移方程:\(dp[u][x]=min(dp[u][x],dp[u][x-y]+dp[v][y]+w\times (k-y)\times y)\),其中 \(u\)为当前结点, \(v\)\(u\) 的父亲结点, \(w\)\(dis(u,v)\)
  • 那么对于每一对父子,我们都可以通过背包去求最优解,每次枚举范围一定是 \(1\sim min(k,siz[u])\)\(siz[u]\) 表示以 \(u\) 为根的子树的叶子数。
  • 只要先选定一个非叶子结点作为根 \(rt\) ,然后一步步向叶子结点转移,最后答案就是 \(dp[rt][k]\)

代码:

struct node{
    int nxt;
    ll w;
};
int n,k;
vector<node>g[100005];
ll dp[100005][105];
int in[100005];
int siz[100005];
void dfs(int u,int fa)
{
    if(in[u]==1){
        siz[u]=1;
        dp[u][1]=0;
    }
    for(int i=0;i<g[u].size();i++){
        int v=g[u][i].nxt;
        ll w=g[u][i].w;
        if(v==fa)continue;
        dfs(v,u);
        siz[u]+=siz[v];
        for(int x=min(k,siz[u]);x>=1;x--){
            for(int y=1;y<=min(x,siz[v]);y++){
                dp[u][x]=min(dp[u][x],dp[u][x-y]+dp[v][y]+w*(k-y)*y);
            }
        }
    }
}
int main()
{
    int T,CASE=0;
    cin>>T;
    while(T--){
        scanf("%d%d",&n,&k);
        for(int i=0;i<=n;i++)g[i].clear();
        mem(in,0);
        mem(siz,0);
        for(int i=1;i<n;i++){
            int u,v;
            ll w;
            scanf("%d%d%lld",&u,&v,&w);
            in[u]++;
            in[v]++;
            g[u].push_back((node){v,w});
            g[v].push_back((node){u,w});
        }
        int rt=1;
        for(int i=1;i<=n;i++){
            if(in[i]>1){
                rt=i;
                break;
            }
        }
        for(int i=1;i<=n;i++){
            dp[i][0]=0;
            for(int j=1;j<=k;j++){
                dp[i][j]=INF;
            }
        }
        dfs(rt,-1);
        printf("Case #%d: ",++CASE);
        printf("%lld\n",dp[rt][k]);
    }
}
posted @ 2021-02-08 21:19  hachuochuo  阅读(132)  评论(0编辑  收藏  举报