[NOI2002]贪吃的九头龙
贪吃的九头龙
题目大意
给出一棵 \(n\) 个点的树,现在要求你给这可树的每个点都染上颜色。你一共有 \(m\) 种颜色,要求第一种颜色必须染恰好 \(k\) 个点,且根节点必须染成第一种颜色,同时其他的每种颜色至少染色一个节点。
当有边相连的两个点为同一颜色时,代价为这条边的权值,否则代价为 \(0\) 。
求给这颗树染色的最小代价。
分析
读完题目后,考虑树形 \(DP\) 。
想一想怎么设状态?最开始会想到设出状态为 \(f[i][j]\) 表示以第 \(i\) 个点为根的子树中有 \(j\) 个节点染成了第一种颜色,但是这样没法更新,我们还需要一个告诉我们当前点颜色的维度。
于是设 \(f[i][j][0/1]\) 表示以第 \(i\) 个点为根的子树中有 \(j\) 个节点染成了第一种颜色,求点 \(i\) 是否染成了第一种颜色。
如何更新?
\[f[u][j][0]=min(f[u][j][0],min(f[to][t][0]+f[u][j-t][0]+(m==2)\times w[i]),f[to][t][1]+f[u][j-t][0]))
\]
\[f[u][j][1]=min(f[u][j][1],min(f[to][t][1]+f[u][j-t][1]+w[i],f[to][t][0]+f[u][j-t][1]))
\]
接下来我们对状态转移方程进行解释。
首先,如果当前节点染色不为 \(1\) ,它和它的儿子节点如果会产生花费,则只有一种情况,就是当 \(m=2\) 时,这两个节点若染色都不为 \(1\) ,则它们只能被染成其他颜色,而其他颜色只有一种,于是很显然会出现贡献。
反之,若 \(m>2\) ,就不会存在这样的情况。
其他地方的更新都比较容易理解,枚举一个 \(j\) ,表 \(u\) 中染色为 \(1\) 的节点数,枚举一个 \(t\) ,表 \(to\) 中染色为 \(1\) 的节点数,之后正常更新即如上。
CODE
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e2+10;
inline int read()
{
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') w*=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
int n,m,k;
int g[N][2],f[N][N][2];
int tot,v[2*N],w[2*N],nex[2*N],first[N];
inline void Add(int x,int y,int z)
{
nex[++tot]=first[x];
first[x]=tot;
v[tot]=y,w[tot]=z;
}
inline void DFS(int u,int fa)
{
f[u][0][0]=f[u][1][1]=0;
for(register int i=first[u];i;i=nex[i]){
int to=v[i];
if(to==fa) continue;
DFS(to,u);
memcpy(g,f[u],sizeof(g));
memset(f[u],63,sizeof(f[u]));
for(register int j=0;j<=k;j++){ //枚举子树中1号染色点的数量
for(register int t=0;t<=j;t++){ //枚举子树to中一号染色点的数量
f[u][j][0]=min(f[u][j][0],min(f[to][t][0]+g[j-t][0]+(m==2)*w[i],f[to][t][1]+g[j-t][0]));
f[u][j][1]=min(f[u][j][1],min(f[to][t][1]+g[j-t][1]+w[i],f[to][t][0]+g[j-t][1]));
}
}
}
}
signed main()
{
n=read(),m=read(),k=read();
if(n-k<m) { puts("-1"); return 0; }
for(register int i=1;i<n;i++){
int x=read(),y=read(),z=read();
Add(x,y,z),Add(y,x,z);
}
memset(f,63,sizeof(f));
DFS(1,1);
printf("%lld\n",f[1][k][1]);
return 0;
}