树形dp贪吃的九头龙(vijos1523)

题目链接:https://vijos.org/p/1523

我们用dp(i,j,k)来表示此状态下最小难受值,i为当前访问到的节点,j为大头还要吃几个,k为当前节点的父节点的颜色。

我们会发现只要小头的数目大于1,我们一定保证当树枝的两端被小头吃时,不会增加难受值,那么难受值就由大头增加。

用1表示大头吃,0表示小头吃。d[i][j]表示当树枝的两端分别被i吃和被j吃时,是否会产生难受值。

d[1][1]=1; d[0][0]=m>2?0:1; d[1][0]=d[0][1]=0;

f[i][j][k]=min(f[son[i]][p][1]+f[brother[i]][j-p-1][k]+cost[i]*d[k][1],f[son[i]][p][0]+f[brother[i]][j-p][k]+cost[i]*d[k][0]);

//分别代表当前节点被大头吃还是被小头吃的情况。

具体的一些剪枝和细节写程序注释里

程序:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
struct ding{
  int to,next,c;
}edge[1000],e[1000];
int f[1000][1000][2],d[2][2];
int num[1000],cost[1000],head[1000],h[1000],n,cnt,son[1000],lef[1000],righ[1000];
bool vis[1000];
void add1(int u,int v,int w){e[++cnt].to=v;e[cnt].next=h[u];e[cnt].c=w;h[u]=cnt;}
void add2(int u,int v){edge[++cnt].to=v; edge[cnt].next=head[u]; head[u]=cnt;}
void dfs(int x)
{
  vis[x]=true;
  for (int i=h[x];i;i=e[i].next)
      if (!vis[e[i].to]) 
      {
        add2(x,e[i].to);
        cost[e[i].to]=e[i].c;
        dfs(e[i].to);
    }
}
int dfs2(int x)
{
   if (x==0) return 0;
   num[x]=dfs2(lef[x])+dfs2(righ[x])+1;
   return num[x];
} 
int dp(int root,int tot,int now)
{ 
  if (tot<0) return 210000000;
//如果已经不需要再取
  if (f[root][tot][now]!=-1) return f[root][tot][now];//记忆化搜索
  if (tot>num[root]) return f[root][tot][now]=210000000;//如果说当前节点的子节点加上兄弟节点的数目还是小于大头要吃的数目,那么永远也取不到,是不合法的。
  if ((root==0)&&(tot==0)) return f[root][tot][now]=0;
//初始节点
  if (root==0) return  f[root][tot][now]=210000000;
//情况不合法
  int ans;  ans=210000000;
  for (int i=0;i<=tot;i++)
  {
      ans=min(ans,dp(lef[root],i,1)+dp(righ[root],tot-i-1,now)+cost[root]*d[now][1]);
    ans=min(ans,dp(lef[root],i,0)+dp(righ[root],tot-i,now)+cost[root]*d[now][0]);
  }
  return f[root][tot][now]=ans;
}
int main()
{
  int k,m;
  scanf("%d%d%d",&n,&m,&k);    
  if (m+k-1>n)
  {
    cout<<"-1"<<endl;
    return 0;
  }//如果果子不够吃,就属特殊情况
  int u,v,c1;
  for (int i=1;i<=n-1;i++)
  {
      scanf("%d%d%d",&u,&v,&c1);
      add1(u,v,c1); add1(v,u,c1);
  }
  cnt=0;
  dfs(1);
//先深搜出这棵树的状态
  for (int j=1;j<=n;j++)
  for (int i=head[j];i;i=edge[i].next)
  {
      int y=edge[i].to;
      if (son[j]) righ[son[j]]=y;
      else lef[j]=y;
      son[j]=y;
//转为二叉树
  }
  d[1][1]=1; if (m==2) d[0][0]=1;
//num[[i]代表i的子树节点加上它的兄弟节点的数量。 
  num[1]=dfs2(lef[1])+dfs2(righ[1])+1;
  for (int i=0;i<=n;i++)
   for (int j=0;j<=k+1;j++)
    for (int z=0;z<=1;z++)
    f[i][j][z]=-1;
  cout<<dp(lef[1],k-1,1)<<endl; 
  return 0;
} 
原文地址:https://www.cnblogs.com/2014nhc/p/6625232.html