题目链接;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 */