JZOJ 5496. 【清华集训2017模拟12.09】Tree

题目

这里写图片描述
数据范围
这里写图片描述

题解

在之前的一道题目中,可以得到一个结论:部分分的条件一定会对解题有帮助。
先看k=n的情况。显然是边数之和*2-直径。
拓展到一般情况,在进行状态转移的时候,要依照条件“边数之和*2-直径”进行转移。
那么有这么几点。
首先,转移的时候,一定保证被转移的状态已经最优,那么怎么在转移的时候保证边数之和 2-直径最小呢?首先这个状态一定存储着当前边数之和2-直径的最小值,然后转移之后的状态还要存储着当前边数之和 2-直径的最小值。
其次,直径的两个端点是否都在x(当前转移的点)上,这个要讨论。
综上所述,设
g[x,i]表示x的子树选了i个点(这i个点都要连通)的所构成的树的边的最小值。
f0[x,i] 表示x的子树选了i个点,直径的一端在x上的所构成的树的边*2-直径的最小值。
f1[x,i] 表示x的子树选了i个点,直径的一端不在x上的所构成的树的边*2-直径的最小值。
那么如何让所有的情况补充不漏呢?操作x点时,儿子的子树一个一个讨论,讨论完合并。这就是树形背包的经典做法
显然转移要讨论三种情况。
①直径在新的子树。②直径在已处理过的部分。
(u,v) 属于的直径能更新答案(对于更新 f1 来说)。
对于 f0 的更新,用含直径的部分的 f0 +不含直径的部分的 g *2更新。
f1的①②跟 f0 差不多。③也挺简单的。

总结

①最重要的一点,操作x点时,儿子的子树一个一个讨论,讨论完合并。
②关于最短路径难以用一次转移式来表示的情况,那么每次保证转移最优即可。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 3010
#define Inf 1061109567
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note{
    int to,next,val;
};note edge[N*2];
int tot,head[N],fa[N],siz[N],ans;
int i,K,n,u,v,z,temp,t1,t2;
int g[N][N],f0[N][N],f1[N][N]; 
void lb(int x,int y,int z){edge[++tot].to=y;edge[tot].next=head[x];edge[tot].val=z;head[x]=tot;}
void mi(int &x,int y){x=x<y?x:y;}
void dg(int x){
    int i,j,k;
    siz[x]=1;f0[x][1]=g[x][1]=0;
    for(i=head[x];i;i=edge[i].next){
        if(fa[x]==edge[i].to)continue;
        fa[edge[i].to]=x;
        dg(edge[i].to);
    }
    for(i=head[x];i;i=edge[i].next){
        if(fa[x]==edge[i].to)continue;
        t1=siz[x]<K?siz[x]:K;
        t2=siz[edge[i].to]<K?siz[edge[i].to]:K;
        fd(j,t1,1)
            fd(k,t2,1)
            if(j+k<=K){
                temp=edge[i].val;
                mi(g[x][j+k],g[x][j]+g[edge[i].to][k]+temp);
                mi(f0[x][j+k],f0[x][j]+2*(g[edge[i].to][k]+temp));
                mi(f0[x][j+k],f0[edge[i].to][k]+2*g[x][j]+temp);
                mi(f1[x][j+k],f1[x][j]+2*(g[edge[i].to][k]+temp));
                mi(f1[x][j+k],f1[edge[i].to][k]+2*(g[x][j]+temp));
                mi(f1[x][j+k],f0[x][j]+f0[edge[i].to][k]+temp);
            }
        siz[x]+=siz[edge[i].to];
    }
}
int main(){
    scanf("%d%d",&n,&K);
    fo(i,1,n-1){
        scanf("%d%d%d",&u,&v,&z);
        lb(u,v,z);lb(v,u,z);
    }
    memset(g,63,sizeof(g));
    memset(f0,63,sizeof(f0));
    memset(f1,63,sizeof(f1));
    dg(1);
    ans=Inf;
    fo(i,1,n)mi(ans,f0[i][K]),mi(ans,f1[i][K]); 
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值