樹形DP專題

題目鏈接:http://blog.csdn.net/liuqiyao_01/article/details/8477730
(在這謝謝dalao的整理)

常見樹形dp

 1.Hdu 1520 Anniversary party 
 題意:每個節點有權值,子節點和父節點不能同時選,問最後能選的最大價值是多少?

 轉移方程解釋:
 dp[root][0]+=max(dp[vec[root][j]][1],dp[vec[root][j]][0]);
 //不要當前根節點,要子點大呢?還是不要子節點大呢?
 dp[root][1]+=dp[vec[root][j]][0];
 //要當前根節點,不要子節點的值
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=6050;


 vector<int> vec[maxn];
 int f[maxn];
 int hap[maxn];
 int dp[maxn][2];

 void dfs(int root)
 {
     int len=vec[root].size();
     dp[root][1]=hap[root];

     for(int j=0;j<len;j++)
        dfs(vec[root][j]);

     for(int j=0;j<len;j++)
     {
         dp[root][0]+=max(dp[vec[root][j]][1],dp[vec[root][j]][0]);
         dp[root][1]+=dp[vec[root][j]][0];
     }
 }
int main()
{
    int n;
    int a,b;
    while(scanf("%d",&n)!=EOF)
    {
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&hap[i]);
            vec[i].clear();
            f[i]=-1;
            dp[i][0]=dp[i][1]=0;
        }

        while(scanf("%d%d",&a,&b))
        {
            if(a==0&&b==0)break;

            f[a]=b;
            vec[b].push_back(a);
        }
        a=1;
        while(f[a]!=-1) a=f[a];
        dfs(a);
        printf("%d\n",max(dp[a][1],dp[a][0]));
    }
    return 0;
}
2.Hdu 2196 Computer
題意: 經典題,求樹每個點到其他點的最遠距離.
解釋: 首先轉化爲有根樹,先處理當前節點到子節點的次長距離和最長距離,一次dfs,再處理當前節點從父節點走去的最長距離.二次dfs.
ps:爲什麼做次長距離?
   可以在下圖看到,綠色節點到子節點最長距離爲2,父節點紅色爲3,可是當我dp綠色節點點時,發現父節點經過自己,那麼這最長距離就不成立了,應該通過藍色次長距離得到最長節點爲3.

這裏寫圖片描述


typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=10010;

 struct Node
 {
     int to;
     int next;
     int len;
 }edge[maxn*2];
 int head[maxn];
 int tol;
 int dp1[maxn];//該節點到葉子節點最大的距離
 int dp2[maxn];//次大距離
 int maxid[maxn];//最大距離對應的序號
 int smaxid[maxn];//次大的序號

 void init()
 {
     tol=0;
     memset(head,-1,sizeof(head));
 }

 void add(int a,int b,int len)
 {
     edge[tol].to=b;
     edge[tol].len=len;
     edge[tol].next=head[a];
     head[a]=tol++;

     edge[tol].to=a;
     edge[tol].len=len;
     edge[tol].next=head[b];
     head[b]=tol++;
 }

 void dfs1(int u,int p)
 {
     dp1[u]=0;
     dp2[u]=0;

     for(int i=head[u];i!=-1;i=edge[i].next)
     {
         int v=edge[i].to;
         if(v==p) continue;
         dfs1(v,u);

         if(dp2[u]<dp1[v]+edge[i].len)
         {
             dp2[u]=dp1[v]+edge[i].len;
             smaxid[u]=v;

             if(dp2[u]>dp1[u])
             {
                 swap(dp2[u],dp1[u]);
                 swap(smaxid[u],maxid[u]);
             }
         }
     }
 }

 void dfs2(int u,int p)
 {
     for(int i=head[u];i!=-1;i=edge[i].next)
     {
         int v=edge[i].to;
         if(v==p)continue;

         if(v==maxid[u])
         {
              if(edge[i].len+dp2[u]>dp2[v])
              {
                  dp2[v]=edge[i].len+dp2[u];
                  smaxid[v]=u;

                  if(dp2[v]>dp1[v])
                  {
                      swap(dp2[v],dp1[v]);
                      swap(smaxid[v],maxid[v]);
                  }

              }
         }
         else
         {
               if(edge[i].len+dp1[u]>dp2[v])
               {
                   dp2[v]=edge[i].len+dp1[u];
                   smaxid[v]=u;

                   if(dp2[v]>dp1[v])
                   {
                       swap(dp2[v],dp1[v]);
                       swap(maxid[v],smaxid[v]);
                   }
               }
         }
         dfs2(v,u);
     }
 }
int main()
{
    int n;
    int v,len;
    while(scanf("%d",&n)!=EOF)
    {
        init();
        for(int i=2;i<=n;i++)
        {
            scanf("%d%d",&v,&len);
            add(i,v,len);
        }
        dfs1(1,-1);

        dfs2(1,-1);
        for(int i=1;i<=n;i++)
            printf("%d\n",dp1[i]);
    }

    return 0;
}

刪點或者刪邊類樹形DP

  1.Hdu 3586 Information Disturbing 
  題意:給定n個敵方據點,1爲司令部,其他點各有一條邊相連構成一棵樹,每條邊都有一個權值cost表示破壞這條邊的費用,葉子節點爲前線。現要切斷前線和司令部的聯繫,每次切斷邊的費用不能超過上限limit,問切斷所有前線與司令部聯繫所花費的總費用少於m時的最小limit
  解釋:首先上限我們是不知道的,但我們只知道切除當前邊的最大上限值 limit,所以我們得二分,雖然可以二分枚舉limit,可是我們發現題目中,切當前邊符合<=limit的邊,加起來的總和不超過m,所以在二分時,我們dp[i]存的值就是sum,若sum<=m,則r=mid.
  對於dp方程
  我們必須切每條邊,我們可以這樣處理,因爲是棵樹,我們可以把邊權附到子節點的點權去.
  然後dp[i]代表切除當前點的最小花費. 然後dp上來即可..
typedef long long ll;
const int INF=1000000+5;
const int maxn=1e5+10;

int val[maxn];
ll dp[maxn];
struct Node
{
    int to;
    int next;
} edge[maxn*2];

int tot;
int head[maxn];

int u,v,w;
void init()
{
    tot=0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
    edge[tot].to=v;
    edge[tot].next=head[u];
    head[u]=tot++;

    edge[tot].to=u;
    edge[tot].next=head[v];
    head[v]=tot++;
}
int n,m;

void dfs(int u,int pre,int tt)
{
    dp[u]=0;

    bool flag=true;//判斷是不是子節點.


    for(int j=head[u]; j!=-1; j=edge[j].next)
    {
        int v=edge[j].to;
        if(v==pre)continue;

        flag=false;
        dfs(v,u,tt);

        dp[u]+=dp[v];
    }
    if(flag)
    {
        if(val[u]<=tt)dp[u]=val[u];
        else dp[u]=INF;

        //cout<<u<<" "<<dp[u]<<endl;
        return ;
    }

    if(u!=1&&val[u]<=tt&&(val[u]<dp[u]))
    {
        dp[u]=val[u];
    }
    //cout<<u<<" "<<dp[u]<<endl;


}
int main()
{
    while(scanf("%d%d",&n,&m),n+m)
    {
        init();
        int lim=0;
        for(int i=1; i<n; i++)
        {
            scanf("%d%d%d",&u,&v,&w);

            lim=max(lim,w);
            val[v]=w;
            addedge(u,v);
        }


        int l=1;
        int r=lim;

        int ans=-1;
        while(l<=r)
        {
            int mid=(l+r)/2;

            dfs(1,-1,mid);

            //cout<<"----->"<<endl;
            if(dp[1]<=m)
            {
                ans=mid;
                r=mid-1;
            }
            else l=mid+1;
        }

        printf("%d\n",ans);
    }
    return 0;
}
2、Poj 3107 Godfather  
題意:刪點,使剩下的分支中最大的節點數最小.
解釋:這道題求的是重心,樹的重心是切除當前節點後,分開的每一塊的最大數最小,就是樹的重心了,所以對於每次先預處理子節點個個數num[v],然後dp[u]=min(dp[u],num[v]),即可知道切除了這個點之後,下面子節點的幾塊的大小,可是還有父節點的那塊怎麼辦? 很簡單啊.. dp[u]=min(dp[u],n-num[u])不就是上面那一塊的大小了咯..這就求出來了。
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=5e4+10;

 int n;
 int u,v;
 struct Node
 {
     int to;
     int next;
 }edge[maxn*2];
 int tot;

 int head[maxn];
 void addedge(int u,int v)
 {
       edge[tot].to=v;
       edge[tot].next=head[u];
       head[u]=tot++;

       edge[tot].to=u;
       edge[tot].next=head[v];
       head[v]=tot++;
 }
 int top;

 int dp[maxn];
 int num[maxn];

 int ans;

 void dfs(int u,int pre)
 {
     dp[u]=0;
     num[u]=1;

     for(int j=head[u];j!=-1;j=edge[j].next)
     {
         int v=edge[j].to;
         if(v==pre) continue;

         dfs(v,u);
         dp[u]=max(dp[u],num[v]);
         num[u]+=num[v];
     }
     dp[u]=max(dp[u],n-num[u]);

     if(dp[u]<ans)
     {
         ans=dp[u];
     }
 }
int main()
{
  while(scanf("%d",&n)!=EOF)
  {
       top=0;
       ans=INF;
       memset(head,-1,sizeof(head));

       for(int i=1;i<n;i++)
       {
           scanf("%d%d",&u,&v);
           addedge(u,v);
       }

       dfs(1,-1);

       bool flag=true;
       for(int i=1;i<=n;i++)
       {
           if(dp[i]==ans)
           {
               if(flag){printf("%d",i);flag=false;}
               else printf(" %d",i);
           }
       }
       printf("\n");


  }
    return 0;
}

3、Poj 2378 Tree Cutting
題意: 刪點,使剩下的分支中有最大節點數的分支小等於總數一半,問有幾種方案。
解釋:同上.dp【u】已經是處理好的,暴力一下dp【u】即可.

typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=1e4+10;

 int n;
 int u,v;
 int dp[maxn];
 int num[maxn];
 struct Node
 {
  int to;
  int next;
 }edge[maxn*2];

 int tot;
 int head[maxn];

 void init()
 {
     tot=0;
     memset(head,-1,sizeof(head));
 }

 void addedge(int u,int v)
 {

      edge[tot].to=v;
      edge[tot].next=head[u];
      head[u]=tot++;

      edge[tot].to=u;
      edge[tot].next=head[v];
      head[v]=tot++;
 }

 void dfs(int u,int pre)
 {
     dp[u]=0;
     num[u]=1;

     for(int j=head[u];j!=-1;j=edge[j].next)
     {
         int v=edge[j].to;
         if(v==pre)continue;
         dfs(v,u);

         dp[u]=max(dp[u],num[v]);
         num[u]+=num[v];
     }
     dp[u]=max(dp[u],n-num[u]);
 }

int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        init();
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        dfs(1,-1);

        bool flag=true;
        for(int i=1;i<=n;i++)
        {
             if(dp[i]<=n/2){printf("%d\n",i);flag=false;}
        }

        if(flag)printf("NONE\n");

    }

    return 0;
}
4、Poj 1655 Balancing Act  
  題意:刪點,使剩下的分支中最大的節點數最小.
  解釋:也同上.只是答案上的處理,dp[u]已經記錄好了.

typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=20010;

 int T;
 int n;
 int u,v;
 vector<int> G[maxn];
 void add(int u,int v)
 {
     G[u].push_back(v);
     G[v].push_back(u);
 }
 int dp[maxn];//對於當前節點的最大的分塊樹;
 int num[maxn];//對於當前節點的子樹個數;

 void dfs(int u,int pre)
 {
     dp[u]=0;
     num[u]=1;

     for(int j=0;j<G[u].size();j++)
     {
         int v=G[u][j];

         if(v==pre)continue;
         dfs(v,u);

         dp[u]=max(dp[u],num[v]);
         num[u]+=num[v];
     }
     dp[u]=max(dp[u],n-num[u]);
 }
int main()
{
   scanf("%d",&T);
   while(T--)
   {
       scanf("%d",&n);
       for(int i=0;i<=n;i++)G[i].clear();
       for(int i=1;i<n;i++)
       {
           scanf("%d%d",&u,&v);
           add(u,v);
       }
       dfs(1,-1);

       int ans=dp[1];
       int ansid=1;

       for(int i=1;i<=n;i++)
       {
           if(ans>dp[i])
           {
               ans=dp[i];
               ansid=i;
           }
       }
       printf("%d %d\n",ansid,ans);

   }



    return 0;
}
5、Poj 3140 Contestants Division  
  題意:刪邊,求刪去某條邊後兩個分支的最小差異值
  解釋:做了上面的題之後其實都水到渠成了,這道也很水了,1次dfs就行了.因爲通過n-num[u]就知道父節點信息.這就沒什麼好說了.
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=1e5+10;



 ll abs1(ll x)
 {
     if(x<0)return -x;
     return x;
 }
 int n,m;
 ll val[maxn];
 ll dp[maxn];
 struct Node
 {
    int to;
    int next;
 }edge[maxn*2];

 int tot;
 int u,v;
 int head[maxn];

 ll sum;
 ll ans;
 bool flag;
 void init()
 {
     flag=true;
     sum=0;
     tot=0;
     memset(head,-1,sizeof(head));
 }
 void addedge(int u,int v)
 {
     edge[tot].to=v;
     edge[tot].next=head[u];
     head[u]=tot++;

     edge[tot].to=u;
     edge[tot].next=head[v];
     head[v]=tot++;
 }


 void dfs(int u,int pre)
 {
     dp[u]=val[u];

     for(int j=head[u];j!=-1;j=edge[j].next)
     {
         int v=edge[j].to;

         if(v==pre) continue;

         dfs(v,u);

         dp[u]+=dp[v];
     }

     if(flag)
     {
         ans=abs1(sum-2*dp[u]);
         flag=false;
     }
     else
     {
         ans=min(ans,abs1(sum-2*dp[u]));
     }
 }
int main()
{
    int Tcase=0;
    while(scanf("%d%d",&n,&m),n+m)
    {
        Tcase++;
        init();
        for(int i=1;i<=n;i++)
        {
            scanf("%I64d",&val[i]);
            sum+=val[i];
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        dfs(1,-1);

        printf("Case %d: %I64d\n",Tcase,ans);


    }

    return 0;
}
(這屬於常見形dp)6、Poj 1741 Tree(難)  
題意:經典題,求樹上兩點間距離小等於K的方案數,樹上分治。
解釋:樹分治,這道題比較難啊只有按上面順序做完纔可能對這題有點理解,我也不太說得清,可以參照kuangbin巨巨的代碼,我也是半理解吧.其他的參考論文吧,我就做了一道難題...
const int MAXN = 10010;
const int INF = 0x3f3f3f3f;
struct Edge
{
    int to,next,w;
}edge[MAXN*2];
int head[MAXN],tot;
void init()
{
    tot = 0;
    memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w)
{
    edge[tot].to = v; edge[tot].w = w;
    edge[tot].next = head[u];head[u] = tot++;
}
bool vis[MAXN];
int size[MAXN],dep[MAXN];
int le,ri;
int dfssize(int u,int pre)
{
    size[u] = 1;
    for(int i = head[u];i != -1;i = edge[i].next)
    {
        int v = edge[i].to;
        if(v == pre || vis[v])continue;
        size[u] += dfssize(v,u);
    }
    return size[u];
}
int minn;
//找重心
void getroot(int u,int pre,int totnum,int &root)
{
    int maxx = totnum - size[u];
    for(int i = head[u];i != -1;i = edge[i].next)
    {
        int v = edge[i].to;
        if(v == pre || vis[v])continue;
        getroot(v,u,totnum,root);
        maxx = max(maxx,size[v]);
    }
    if(maxx < minn){minn = maxx; root = u;}
}
void dfsdepth(int u,int pre,int d)
{
    dep[ri++] = d;
    //cout<<"->"<<u<<endl;
    for(int i = head[u];i != -1;i = edge[i].next)
    {
        int v = edge[i].to;

        if(v == pre || vis[v])continue;
        dfsdepth(v,u,d+edge[i].w);
    }
}
int k;
int getdep(int a,int b)
{
    sort(dep+a,dep+b);
    int ret = 0, e = b-1;
    for(int i = a;i < b;i++)
    {
        if(dep[i] > k)break;
        while(e >= a && dep[e] + dep[i] > k)
        {
            e--;
        }
        //這個地方是個技巧,我們先預處理在子節點內所有 dep[e] + dep[i]<= k的情況
        //然後再用把當前根節點的遍歷一遍,減去這些情況,就得到跨當前根節點的個數了.
         //cout<<"e:"<<e<<" "<<dep[e]<<"-----"<<"i:"<<i<<" "<<dep[i]<<endl;
        ret += e - a + 1;
        if(e >= i)ret--;
    }
    return ret>>1;
}
int solve(int u)
{
    int totnum = dfssize(u,-1);
    int ret = 0;
    minn = INF;
    int root;
    getroot(u,-1,totnum,root);
   // cout<<"root--->"<<root<<endl;
    vis[root] = true;
    for(int i = head[root];i != -1;i = edge[i].next)
    {
        int v = edge[i].to;
        if(vis[v])continue;
        ret += solve(v);
    }
    le = ri = 0;
    for(int i = head[root];i != -1;i = edge[i].next)
    {
        int v = edge[i].to;
        if(vis[v])continue;
        dfsdepth(v,root,edge[i].w);
        //cout<<"------------>"<<endl;
        ret -= getdep(le,ri);
        le = ri;
    }
    ret += getdep(0,ri);
    //cout<<"------------>"<<ret<<endl;
    for(int i = 0;i < ri;i++)
    {
        if(dep[i] <= k)ret++;
        else break;
    }
    vis[root] = false;
    return ret;
}

int main()
{
    int n;
    int u,v,w;
    while(scanf("%d%d",&n,&k) == 2)
    {
        if(n == 0 && k == 0)break;
        init();
        for(int i = 1;i < n;i++)
        {
            scanf("%d%d%d",&u,&v,&w);
            addedge(u,v,w);
            addedge(v,u,w);
        }
        memset(vis,false,sizeof(vis));
        printf("%d\n",solve(1));
    }
    return 0;
}

樹形揹包問題(在樹上進行分組揹包處理)

引入kuangbin巨巨的分組揹包思想:



狀態轉移,使用的“分組揹包”思想。
使用一維數組的“分組揹包”僞代碼如下:
for 所有的組i
for v=V..0
for 所有的k屬於組i
f[v]=max{f[v],f[v-c[k]]+w[k]}


個人感想:
      理解分組揹包,整是開始分組揹包的前提了.好了,我根據我做的題目我一個個列出來我初步的思想. (對於剛開始的時候我某些是借鑑別人的,之後就越做越順了…)


   1、Poj 1155 TELE 
   題意:某電臺要廣播一場比賽,該電臺網絡是由N個網點組成的一棵樹,其中M個點爲客戶端,其餘點爲轉發站。客戶端i願支付的錢爲pay[i],每一條邊需要的花費固定,問電臺在保證不虧損的情況下,最多能使多少個客戶端接收到信息?
   解釋:這道題我剛開始也是半懂的感覺,所以代碼是抄人家的,因爲我也不太會哦,只有理解了那個分組思想之後才能明白.
   dp[i][j]:從當前u幾點供給j個用戶的獲得的最大利潤.  
   用上面的分組揹包思想做就好了,莫非就是選和不選的問題,然後dp好了之後 遍歷一下根節點哪一次dp[root][j]>=0的即可.. 就可以了.說得有點迷,看下代碼就理解了.
#define MAXN 3010

struct Node{
    int v,w,next;
}edge[MAXN*MAXN];

int dp[MAXN][MAXN];
int num[MAXN];  //容量
int head[MAXN];

int max(int a,int b)
{
    return a>b?a:b;
}

void dfs(int u)
{
    int i,j,k,v;
    for(i=head[u];i!=-1;i=edge[i].next)
    {
        v=edge[i].v;
        dfs(v);
        num[u]+=num[v];

        for(j=num[u];j>=1;j--)  //揹包 需要逆向
            for(k=1;k<=num[v];k++)
                dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-edge[i].w);
    }
}
int v,w;
int main()
{
    int i,j,n,m,k,cnt=0;
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for(i=1;i<=n-m;i++)
    {
        scanf("%d",&k);
        while(k--)
        {
            scanf("%d%d",&v,&w);
            edge[cnt].v=v;
            edge[cnt].w=w;
            edge[cnt].next=head[i];
            head[i]=cnt++;
        }
    }
    for(i=1;i<=n;i++)
        for(j=1;j<=m;j++)
            dp[i][j]=-1e9;//因爲狀態有負值 所以不能爲0
    for(i=n-m+1;i<=n;i++)
    {
        num[i]=1;
        scanf("%d",&dp[i][1]);
    }
    dfs(1);
    for(i=num[1];i>=0;i--)
        if(dp[1][i]>=0)
            break;
    printf("%d",i);
    return 0;
}
  2、Hdu 1011 Starship Troopers 
  題意:給出每個房間擁有的BUG數和能得到的能量數,然後給出每個房間的聯通圖,要到下一個房間必須攻破上一個房間,每個士兵最多消滅20個BUG,就算不足20個BUG也要安排一個士兵
  解釋:和上面分組揹包,一毛一樣,可以先預處理每個點的權值,然後再進行dp,而且這裏有個坑點就是m==0時要特殊處理,注意下就行了
  dp[u][j]:對於當前節點送j個士兵下去獲得的最大權值.
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=110;

 /*
    簡單樹形dp

    dp[u][j]:在當前u節點,留下j個兵獲去其他路(包括必定在這個節點)取得到的最大金額,
    轉移方程: dp[u][s]=max(dp[u][s],dp[u][s-j]+dp[v][j]);,留s-j個在u點獲取的最大金額(包括走其他支),剩下j個兵走v獲得的最大值.
 */

 struct Node
 {
     int cost;
     int val;
 }room[maxn];

 int dp[maxn][maxn];
 vector<int> G[maxn];
 int n,m;
 bool vis[maxn];


 void dfs(int u)
 {
     vis[u]=true;
     memset(dp[u],0,sizeof(dp[u]));

     if(!room[u].cost&& G[u].size()==1 && u!=1)room[u].cost=1;

     for(int j=room[u].cost;j<=m;j++)
         dp[u][j]=room[u].val;

     for(int j=0;j<G[u].size();j++)
     {
         int v=G[u][j];
         if(vis[v])continue;

         dfs(v);
         for(int s=m;s>=room[u].cost;s--)
         {
             for(int j=1;s-j>=room[u].cost;j++)
             {
                 dp[u][s]=max(dp[u][s],dp[u][s-j]+dp[v][j]);
             }
         }
     }


 }
int main()
{
  while(scanf("%d%d",&n,&m)!=EOF)
  {
      if(n==-1&&m==-1)break;

      for(int i=1;i<=n;i++)G[i].clear();

      int x;
      for(int i=1;i<=n;i++)
      {
          scanf("%d%d",&x,&room[i].val);
          room[i].cost=x/20+(x%20!=0);
      }

      for(int i=0;i<n-1;i++)
      {
          int u,v;
          scanf("%d%d",&u,&v);
          G[u].push_back(v);
          G[v].push_back(u);
      }

      if(m==0)
      {
         printf("0\n");
         continue;
      }

      memset(vis,0,sizeof(vis));
      vis[1]=true;
      dfs(1);
      printf("%d\n",dp[1][m]);
  }

    return 0;
}
3、Poj 1947 Rebuilding Roads 求
題意:最少刪除幾條邊使得子樹節點個數爲p.
解釋:具體模型都和上面相似
dp[u][j]:代表當前節點,獲得j個點的最少刪邊數.
typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=200;

 int N,P;
 vector<int> G[maxn];
 int dp[maxn][maxn];
 int pre[maxn];
 void init()
 {
     for(int i=1;i<=N;i++)
     {
         G[i].clear();
         pre[i]=i;
     }
 }
 void getMap()
 {
     int a,b;
     for(int i=1;i<N;i++)
     {
         scanf("%d%d",&a,&b);
         G[a].push_back(b);
         pre[b]=a;
     }
 }
void dfs(int u)
{
    dp[u][1] = 0;//初始 自己不刪邊
    for(int i = 0; i < G[u].size(); i++)
    {
        int v = G[u][i];
        dfs(v);
        for(int j = P; j >= 0; j--)
        {
            int t = dp[u][j] + 1;//直接刪掉 與 子節點v 相連的邊
            for(int k = 0; k <= j; k++)
                t = min(t, dp[u][j-k] + dp[v][k]);
            dp[u][j] = t;
        }
    }
}
 void solve()
 {
     int root;
     for(int i=1;i<=N;i++)
     {
         if(pre[i]==i)
         {
             root=i;
             break;
         }
     }

     memset(dp,0x3f,sizeof(dp));
     dfs(root);
     int ans=INF;
     for(int i=1;i<=N;i++)
     {
         if(i==root)
             ans=min(ans,dp[i][P]);
         else
             ans=min(ans,dp[i][P]+1);
     }
     printf("%d\n", ans);
 }
int main()
{
    while(scanf("%d%d",&N,&P)!=EOF)
    {
          init();
          getMap();
          solve();
    }

    return 0;
}
  4、Hdu 1561 The more, The Better 
  題意:在一棵樹上選擇若干個點獲得的最大價值,選子節點必須先選父節點.
  解釋:這不就是剛剛派兵打bugs的題目嗎..就是一毛一樣啊..沒什麼好說的啊。。只是要多加一個虛擬節點.所以選點要到m+1鏈接成有根樹就行了..很簡單哦.

typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=205;

 vector<int> G[maxn];
 int N,M;
 int v;
 ll dp[maxn][maxn];
 ll val[maxn];
 void dfs(int u,int pre)
 {
      for(int j=0;j<G[u].size();j++)
      {
          int v=G[u][j];
          if(v==pre) continue;

          dfs(v,u);
          for(int t=M+1;t>=1;t--)
          {
              for(int k=1;t-k>=1;k++)
              {
                  dp[u][t]=max(dp[u][t],dp[u][t-k]+dp[v][k]);
              }
          }
      }

 }
int main()
{
    while(scanf("%d%d",&N,&M),(N+M))
    {

         memset(dp,0,sizeof(dp));
         for(int i=0;i<=N;i++)G[i].clear();

         val[0]=0;
         for(int i=1;i<=N;i++)
         {
             scanf("%d%I64d",&v,&val[i]);
             G[i].push_back(v);
             G[v].push_back(i);

             for(int j=1;j<=M+1;j++)
             {
                 dp[i][j]=val[i];
             }
         }
         dfs(0,-1);
         printf("%I64d\n",dp[0][M+1]);
    }

    return 0;
}
 5、Hdu 4003 Find Metal Mineral (推薦,好題) 
 題意:樹形DP+選且只能選一個物品的分組揹包
 解釋:狀態轉移方程難想,這直接看kuangbin巨巨寫的吧,他的分組思想也寫在那
 http://www.cnblogs.com/kuangbin/archive/2012/08/29/2661928.html

typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=1e4+10;

 int N,S,K;
 struct Node
 {
    int to;
    int next;
    int w;
 }edge[maxn*2];

 int tot;
 int head[maxn];
 int u,v,w;
 int dp[maxn][15];
 void init()
 {
     tot=0;
     memset(head,-1,sizeof(head));
     memset(dp,0,sizeof(dp));
 }

 void addedge(int u,int v,int w)
 {
     edge[tot].to=v;
     edge[tot].next=head[u];
     edge[tot].w=w;
     head[u]=tot++;

     edge[tot].to=u;
     edge[tot].next=head[v];
     edge[tot].w=w;
     head[v]=tot++;
 }

 void dfs(int u,int pre)
 {

       for(int j=head[u];j!=-1;j=edge[j].next)
       {
           int v=edge[j].to;
           if(v==pre) continue;
           dfs(v,u);
           for(int k=K;k>=0;k--)
           {
                dp[u][k]+=(dp[v][0]+2*edge[j].w);
                for(int t=1;t<=k;t++)
                {
                   dp[u][k]=min(dp[u][k],dp[u][k-t]+dp[v][t]+t*edge[j].w);
                }
           }

       }

 }
int main()
{
    while(scanf("%d%d%d",&N,&S,&K)!=EOF)
    {
          init();
          for(int i=1;i<=N-1;i++)
          {
              scanf("%d%d%d",&u,&v,&w);
              addedge(u,v,w);
          }

          dfs(S,-1);
          printf("%d\n",dp[S][K]);


    }

    return 0;
}
  6、Poj 2486 Apple Tree 
  題意:一顆樹,n個點(1-n),n-1條邊,每個點上有一個權值,求從1出發,走V步,最多能遍歷到的權值
  解釋:看巨巨鏈接,寫得太清楚...又容易理解.
  http://blog.csdn.net/libin56842/article/details/10101807

typedef long long ll;
const int INF=0x3f3f3f3f;
const int maxn=100+10;

 int N,K;
 int val[maxn];

 struct Node
 {
     int to;
     int next;
 }edge[maxn*2];

 int tot;
 int head[maxn];
 int dp[maxn][205][2];
 int u,v;

 void init()
 {
     tot=0;
     memset(dp,0,sizeof(dp));
     memset(head,-1,sizeof(head));
 }
 void addedge(int u,int v)
 {
      edge[tot].to=v;
      edge[tot].next=head[u];
      head[u]=tot++;

      edge[tot].to=u;
      edge[tot].next=head[v];
      head[v]=tot++;
 }
 void dfs(int u,int pre)
 {

     for(int j=head[u];j!=-1;j=edge[j].next)
     {
         int v=edge[j].to;
         if(v==pre) continue;
         dfs(v,u);

         for(int k=K;k>=0;k--)
         {
             for(int t=1;t<=k;t++)
             {

               dp[u][k][1]=max(dp[u][k][1],dp[u][k-t][0]+dp[v][t-1][1]);
               if(t-2>=0)
               {
                   dp[u][k][0]=max(dp[u][k][0],dp[u][k-t][0]+dp[v][t-2][0]);
                   dp[u][k][1]=max(dp[u][k][1],dp[u][k-t][1]+dp[v][t-2][0]);
               }

             }
         }

     }
 }
int main()
{
    while(scanf("%d%d",&N,&K)!=EOF)
    {
        init();
        for(int i=1;i<=N;i++)
        {
            scanf("%d",&val[i]);
            for(int j=0;j<=K;j++){dp[i][j][0]=dp[i][j][1]=val[i];}
        }

        for(int i=1;i<N;i++)
        {
            scanf("%d%d",&u,&v);
            addedge(u,v);
        }
        dfs(1,-1);
        printf("%d\n",max(dp[1][K][0],dp[1][K][1]));

    }

    return 0;
}
 7、Poj 3345 Bribing FIPA  
 題意:現在有n個村子,你想要用收買m個村子爲你投票,其中收買第i個村子的代價是val[i]。但是有些村子存在從屬關係,如果B從屬於A國,則收買了A也意味着買通了B,而且這些關係是傳遞的。問你最小要付出的代價是多少?
 解釋:這道題其實很簡單,不過稍微有點難理解
 我是借鑑了
 http://blog.sina.com.cn/s/blog_6635898a0100qrdl.html 大牛的博客,不過dp方程我是自己寫的,因爲有點懶這道題目的輸入好麻煩哦,我就不想寫那麼多,我就寫dp方程,而且我對巨巨的dp方程也有點不贊同,我的思想是相反的,就是dp[i][j] 代表在當前獲取j張個投票權的所需要的最小花費.我寫的方程和巨巨不一樣,看哪個好理解咯.
const int MAX = 205;
const int INF = 99999999;

struct{
    int v, nxt;
}edge[MAX];
int k, edgeHead[MAX];
int n, m, mm;
char name[MAX][105];
int num[MAX], val[MAX];
int tmp[MAX], dp[MAX][MAX];
bool rt[MAX];

void addEdge(int u, int v){
    edge[k].v = v;
    edge[k].nxt = edgeHead[u];
    edgeHead[u] = k ++;
}

void dfs(int u){
    int i, j, k, v;
    if(u!=0)num[u] = 1;
    else num[u]=0;
    dp[u][1] = val[u];
    for(i = edgeHead[u]; i; i = edge[i].nxt)
    {
        v = edge[i].v;
        dfs(v);
        num[u] += num[v];

        for(int j=num[u];j>=0;j--)
        {
            dp[u][j]=min(dp[u][j],val[u]);

            for(int t=0;t<=num[v]&&(j+t)<=num[u];t++)
            {
                 dp[u][j+t]=min(dp[u][j+t],dp[u][j]+dp[v][t]);
            }
        }
    }

}

int search(char *str){
    for(int i = 1; i <= mm; i ++)
        if(!strcmp(str, name[i]))
            return i;
    strcpy(name[mm ++], str);
    return mm-1;
}

int main()
{
    int i, j, u, v, va;
    char str[105];
    while(gets(str) && str[0] != '#'){
        sscanf(str, "%d%d", &n, &m);     // sscanf()函數應用在這裏很適合:把str當爲輸入流。
        memset(name, 0, sizeof(name));
        for(i = 0; i <= n; i ++){
            for(j = 1; j <= n; j ++)
                dp[i][j] = INF;
            dp[i][0] = 0;
            edgeHead[i] = 0;
            rt[i] = true;
        }
        mm = 1, k = 1;
        for(i = 1; i <= n; i ++){
            scanf("%s%d", str, &va);
            u = search(str);
            val[u] = va;
            while(getchar() != '\n'){
                scanf("%s", str);
                v = search(str);
                rt[v] = false;
                addEdge(u, v);
            }
        }
        val[0] = INF;
        for(i = 1; i <= n; i ++)    //  加入總根。
            if(rt[i])
                addEdge(0, i);
        dfs(0);
        printf("%d\n", dp[0][m]);
    }
    return 0;
}

總結

其實樹形dp真的很強大,這不是簡簡單單可以像數位dp一樣,套個模板即可…要真正理解還是得花心思的..慢慢來,腳踏實地的做,理解好了比刷題數多更重要..

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章