题目链接;http://www.freesion.com/article/8602142306/
题目大意:


思路:
u是v的直连父亲,先往下搜,向上回溯时,

枚举边计算贡献,即u和v之间边w,v里面选了p个,则all-v这一块选k-p个

边w被经过p*(k-p)次,

实际转移时,考虑v里取了p个,u在已经搜过的子树里取了q个,

以此来更新dp[u][p+q]的值

dp[u][p]表示在u这棵子树(含u)里选了p个点的最小代价

具体实现时,应考虑p+q<=k,

用加法p+q比用减法p-q快,但考虑更新顺序,故用辅助数组

为什么用辅助数组?,因为

从大到小更新可以解决这个问题,或者用辅助数组。再结束后再更新。

#include <bits/stdc++.h>
#define LL long long
const LL INF=0x3f3f3f3f3f3f3f3fll;
using namespace std;
struct node{
    int to, w;
};

vector<node> v[50005];
int s[50005]={0};
LL num[50005]={0};
LL f[50005][105]={0}, t[105];
int n, m, k;
void dfs(int u, int fa){

    f[u][0]=0;
    if(s[u]==1){
        f[u][1]=0;
        num[u]=1;
    }
    for(int r=0; r<v[u].size(); r++){
        int to=v[u][r].to, w=v[u][r].w;
        if(to!=fa){
            dfs(to, u);

            int sum=min(1ll*k ,num[u]+num[to]), mn=min(1ll*k, num[u]);
            for(int i=0; i<=sum; i++){
                t[i]=f[u][i];
            }

            for(int i=0; i<=mn; i++){//其余子树i个
                for(int j=0; j+i<=sum; j++){//当前子树j个
                    t[i+j]=min(t[i+j], f[u][i]+f[to][j]+(j)*(k-j)*1ll*w);
                }
            }

            for(int i=0; i<=sum; i++){
                f[u][i]=min(f[u][i], t[i]);
            }
            num[u]+=num[to];
        }
    }
}

int main()
{
    int u, to, w;
    scanf("%d%d%d", &n, &m, &k);
    for(int i=1; i<=m; i++){
        scanf("%d", &u);
        s[u]=1;
    }
    memset(f, INF, sizeof(f));
    //cout<<f[1][1]<<endl;
    for(int i=1; i<=n-1; i++){
        scanf("%d%d%d", &u, &to, &w);
        v[u].push_back(node{to, w});
        v[to].push_back(node{u, w});
    }
    dfs(1, -1);
    printf("%lld\n", f[1][k]);

    return 0;
}
/* 5 3 2 1 3 5 1 2 4 1 3 5 1 4 3 5 4 1 4 */