题目:
题意:
给出一棵有
n
n
n个节点的树,每条边有一个距离,每个点有一个权值
我们需要选出除根节点以外的
k
k
k个节点,每个点的答案为该点的点权向上直到第一个选出的节点,表示为
w
∗
d
i
s
t
w*dist
w∗dist
求最小的答案
分析:
首先想一想我们需要记录哪些信息:现在在哪个点、这个点的选择如何、到目前为止一共选了几个点
于是有
f
i
,
s
,
k
f_{i,s,k}
fi,s,k表示现在位于
i
i
i,选择状态为
s
s
s,在
i
i
i的子树中一共选择了
k
k
k个点
但在处理的过程中我发现,这样的状态表示方法看起来没啥问题,但好像答案无法正确的去计算
经过思考,我们发现某一部分的所有答案都会在一个固定的点,即连他们最近的选择的点,所以我们再新增一个状态,表示最近选择的点是哪个
更新状态
f
i
,
f
a
,
k
&
g
i
,
f
a
,
k
f_{i,fa,k}\&g_{i,fa,k}
fi,fa,k&gi,fa,k表示当前在
i
i
i,距离最近被选择的是
f
a
fa
fa,以
i
i
i为根的子树内有
k
k
k个点被选择,
f
f
f表示不选择
i
i
i,
g
g
g表示选择
在处理完一个点的整个子树后因为我们只需最优解,并不刻意
i
i
i的选择情况,所以将两个合并成一个就好咯
代码:
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
using namespace std;
inline LL read()
{
LL s=0,f=1; char c=getchar();
while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') {s=s*10+c-'0';c=getchar();}
return s*f;
}
struct node{
int to,next,w;
}e[205];
int ls[105],cnt=0;
void add(int x,int y,int w)
{
e[cnt]=(node){y,ls[x],w};
ls[x]=cnt++;
return;
}
int f[105][105][55],g[105][105][55];
int s[105],size=0,dis[105],w[105];
int n=read(),k=read();
void dfs(int u,int fa)
{
s[++size]=u;
for(int i=ls[u];~i;i=e[i].next)
{
int v=e[i].to;
if(fa==v) continue;
dis[v]=dis[u]+e[i].w;
dfs(v,u);
for(int j=1;j<=size;j++)
for(int x=k;x>=0;x--)
{
f[u][s[j]][x]+=f[v][s[j]][0];
g[u][s[j]][x]+=f[v][u][0];
for(int y=1;y<=x;y++)
{
f[u][s[j]][x]=min(f[u][s[j]][x],f[u][s[j]][x-y]+f[v][s[j]][y]);
g[u][s[j]][x]=min(g[u][s[j]][x],g[u][s[j]][x-y]+f[v][u][y]);
}
}
}
for(int i=1;i<=size;i++)
{
f[u][s[i]][0]+=w[u]*(dis[u]-dis[s[i]]);
for(int x=1;x<=k;x++)
f[u][s[i]][x]=min(f[u][s[i]][x]+w[u]*(dis[u]-dis[s[i]]),g[u][s[i]][x-1]);
}
size--;
return;
}
int main()
{
memset(ls,-1,sizeof(ls));
for(int i=1;i<=n;i++)
{
int x=i+1;
w[x]=read();
int y=read()+1,W=read();
add(x,y,W);add(y,x,W);
}
dfs(1,0);
cout<<f[1][1][k];
return 0;
}