题解:[NOI2002]贪吃的九头龙

Sol

题目大意
有一棵树,你需要将N个点染成M种颜色,且需满足一下两种条件
1.一号点必须是一号颜色,且一号颜色必须包含K个点
2.每种颜色必须包含至少一个点
代价为若一条边连接的颜色相同则得付出该边的代价
求满足以上两种情况下的代价之和

题解

首先,一眼看出是道树dp题
然后日常套路,设 f[u][j] f [ u ] [ j ] 表示以 u u 为子树包含j种一号颜色下的最小代价,然后发现无法判断两个点之间是否颜色相同,于是再加一维 f[u][j][0/1] f [ u ] [ j ] [ 0 / 1 ] 表示在 u u <script type="math/tex" id="MathJax-Element-480">u</script>号点上是否染为一号颜色
然后开始最重要的yy
如果M==2的话,一条边连接的两个点只有一个为1一个不为1的情况下才不会付出代价
如果M>2的话,一条边连接的两个点只要不是都为1就不会付出代价
为什么呢?
显然可以通过奇偶性来强制两个点颜色不同,且满足条件
然后dp方程就是

f[u][j][0]=min(f[u][j][0],min(f[v][t][0]+f[u][j-t][0]+(M==2)*e[i].w,f[v][t][1]+f[u][j-t][0]));
f[u][j][1]=min(f[u][j][1],min(f[v][t][1]+f[u][j-t][1]+e[i].w,f[v][t][0]+f[u][j-t][1]));

然后惊奇的发现其实我们连样例都过不了(尴尬)
于是重新审视dp方程
我们需要每次dp前将之前的f数组备份下来,不然的话f值在dp中会越来越小,当然如果N也很小的话其实也没什么影响
所以我们用tmp[j][0/1]来存f[u]数组的值
f[u][j][0]=min(f[u][j][0],min(f[v][t][0]+tmp[j-t][0]+(M==2)*e[i].w,f[v][t][1]+tmp[j-t][0]));
f[u][j][1]=min(f[u][j][1],min(f[v][t][1]+tmp[j-t][1]+e[i].w,f[v][t][0]+tmp[j-t][1]));

然后就能愉快的AC了

好了,上代码

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int _=305;
inline int read()
{
    char ch='!';int z=1,num=0;
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')z=-1,ch=getchar();
    while(ch<='9'&&ch>='0')num=(num<<3)+(num<<1)+ch-'0',ch=getchar();
    return z*num;
}
int N,M,K,f[_][_][2],tmp[_][2];
struct hand{int to,next,w;}e[_<<1];
int cnt,head[_];
void link(int u,int v,int w)
{
    e[++cnt]=(hand){v,head[u],w};
    head[u]=cnt;
}
void dfs(int u,int fa)
{
    f[u][0][0]=f[u][1][1]=0;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v==fa)continue;
        dfs(v,u);
        memcpy(tmp,f[u],sizeof(tmp));
        memset(f[u],63,sizeof(f[u]));
        for(int j=0;j<=K;++j)
        {
            for(int t=0;t<=j;++t)
            {
                f[u][j][0]=min(f[u][j][0],min(f[v][t][0]+tmp[j-t][0]+(M==2)*e[i].w,f[v][t][1]+tmp[j-t][0]));
                f[u][j][1]=min(f[u][j][1],min(f[v][t][1]+tmp[j-t][1]+e[i].w,f[v][t][0]+tmp[j-t][1]));
            }
        }
    }
}
int main()
{
    N=read();M=read();K=read();
    if(N-K<M-1){puts("-1");return 0;}
    for(int i=1;i<N;++i)
    {
        int x=read(),y=read(),z=read();
        link(x,y,z);link(y,x,z);
    }
    memset(f,63,sizeof(f));
    dfs(1,1);
    printf("%d\n",f[1][K][1]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值