BZOJ4987:Tree (树形DP)

Description

从前有棵树。
找出K个点A1,A2,…,Ak。
使得∑dis(AiAi+1),(1<=i<=K-1)最小。

Input

第一行两个正整数n,k,表示数的顶点数和需要选出的点个数。
接下来n-l行每行3个非负整数x,y,z,表示从存在一条从x到y权值为z的边。
I<=k<=n。
l<x,y<=n
1<=z<=10^5
n <= 3000

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

思路:求大小为K的连通块的最小遍历距离,最小值显然等于边权之和*2-直径。 我们利用这个来DP,用dp[i][j]表示i子树有j个直径的端点(i<=N,j<=2); 如果我i子树有两个直径端点,则当前边不在直径上,如果子树有一个直径端点,则当前边再直径上....其他类似

然后就不难得到方程。 注意合并的时候要01像背包一样从大到小,以免一个数用两次。  (或者用一个临时变量数组)

#include<bits/stdc++.h>
using namespace std;
const int maxn=3010;
int Laxt[maxn],Next[maxn<<1],To[maxn<<1],Len[maxn<<1],cnt;
int N,K,sz[maxn],dp[maxn][maxn][3],ans;
void add(int u,int v,int w){
    Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; Len[cnt]=w;
}
void dfs(int u,int fa){
    sz[u]=1; dp[u][1][0]=0; dp[u][1][1]=0; dp[u][1][2]=0;
    for(int i=Laxt[u];i;i=Next[i]){
        int v=To[i]; if(v==fa) continue;
        dfs(v,u);
        for(int j=sz[u];j>=0;j--){
            for(int k=sz[v];k>=0;k--){
                dp[u][j+k][2]=min(dp[u][j+k][2],dp[u][j][0]+dp[v][k][2]+2*Len[i]);
                dp[u][j+k][2]=min(dp[u][j+k][2],dp[u][j][1]+dp[v][k][1]+Len[i]);
                dp[u][j+k][2]=min(dp[u][j+k][2],dp[u][j][2]+dp[v][k][0]+2*Len[i]);

                dp[u][j+k][1]=min(dp[u][j+k][1],dp[u][j][0]+dp[v][k][1]+Len[i]);
                dp[u][j+k][1]=min(dp[u][j+k][1],dp[u][j][1]+dp[v][k][0]+2*Len[i]);

                dp[u][j+k][0]=min(dp[u][j+k][0],dp[u][j][0]+dp[v][k][0]+2*Len[i]);
            }
        }
        sz[u]+=sz[v];
    }
    ans=min(ans,dp[u][K][2]);
}
int main()
{
    int u,v,w;
    scanf("%d%d",&N,&K);
    memset(dp,0x3f,sizeof(dp));
    for(int i=1;i<N;i++){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w); add(v,u,w);
    }
    ans=0x3f3f3f3f;
    dfs(1,0);
    printf("%d\n",ans);
    return 0;
}

 

posted @ 2018-11-12 11:08  nimphy  阅读(265)  评论(0编辑  收藏