【清华集训2017模拟12.09】Tree

该博客介绍了2017年清华集训模拟赛中的一道题目,涉及树的最短距离和的计算。博主通过动态规划(dp)的方法来求解问题,定义了g[i][j]表示以节点i为根的子树中选取j个点,形成的虚树总长度的最小值。同时,博主给出了f0[i][j]和f1[i][j]两个状态,分别表示最长链端点的不同情况。虽然看似复杂度为n^3,但实际复杂度为n^2。文章最后贴出了源代码作为解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description
这里写图片描述

Input

这里写图片描述

Output

一行一个整数, 表示最小的距离和。

Sample Input

10 7
1 2 35129
2 3 42976
3 4 24497
2 5 83165
1 6 4748
5 7 38311
4 8 70052
3 9 3561
8 10 80238

Sample Output

184524

Data Constraint
这里写图片描述

题解

设g[i][j] 为以i为根的子树选择了j个点,它们构成的虚树的总长的最小值
f0[i][j]为i为根选了j个点,且当前的最长链的一个端点的i的2*边-链长的最小值
f1定义与f0相似,但是链不过端点(其实也可以过,但是不会比对应的f1更优,所以其实没有影响)

然后就按照链在子树内的情况讨论dp就可以了
看上去好像是n^3的,但是实际上复杂度是fa[x]=fa[y]size[x]size[y]把它拆开时候发现是n^2的

贴代码

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fo1(i,b,a) for(i=b;i>=a;i--)
#define min(x,y) ((x)<(y)?(x):(y))
#define ll long long
using namespace std;

const int maxn=3005;

int fi[maxn],ne[maxn*2],dui[maxn*2],dui1[maxn*2],qc[maxn],s[maxn];
ll f0[maxn][maxn],f1[maxn][maxn],g[maxn][maxn];
int i,j,k,l,m,n,x,y,z,now;
ll ans,b;

void add(int x,int y){
    if (fi[x]==0) fi[x]=++now; else ne[qc[x]]=++now;
    qc[x]=now; dui[now]=y; dui1[now]=z;
}
void dfs(int x,int y){
    int i=fi[x];
    s[x]=1; g[x][1]=f0[x][1]=f1[x][1]=0;
    while (i){
        if (dui[i]==y){
            i=ne[i];
            continue;
        }
        dfs(dui[i],x); z=dui[i];
        fo1(j,min(m,s[x]),1){
            fo1(k,min(s[dui[i]],m-j),1){
                f1[x][j+k]=min(f1[x][j+k],b*g[x][j]+f1[z][k]+b*dui1[i]);
                f1[x][j+k]=min(f1[x][j+k],f0[x][j]+f0[z][k]+dui1[i]);
                f1[x][j+k]=min(f1[x][j+k],f1[x][j]+b*g[z][k]+b*dui1[i]);
                f0[x][j+k]=min(f0[x][j+k],b*g[x][j]+f0[z][k]+dui1[i]);
                f0[x][j+k]=min(f0[x][j+k],f0[x][j]+b*g[z][k]+b*dui1[i]);
                g[x][j+k]=min(g[x][j+k],g[x][j]+g[z][k]+dui1[i]);
            }
        }
        s[x]=s[x]+s[dui[i]];
        i=ne[i];
    }
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,n-1){
        scanf("%d%d%d",&x,&y,&z);
        add(x,y); add(y,x);
    }
    memset(g,1,sizeof(g));
    memset(f0,1,sizeof(f0));
    memset(f1,1,sizeof(f1)); b=2;
    dfs(1,0);
    ans=min(f0[1][m],f1[1][m]);
    fo(i,2,n){
        ans=min(ans,min(f0[i][m],f1[i][m]));
    }
    printf("%lld\n",ans);
    return 0;   
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值