树形DP 入门资料

树形DP

二叉苹果树(ural 1108)

 

题目意思:

有一棵苹果树,苹果树的是一棵二叉树,共N个节点,树节点编号为1~N,编号为1的节点为树根,边可理解为树的分枝,每个分支都长着若干个苹果,现在要要求减去若干个分支,保留M个分支,要求这M个分支的苹果数量最多。

 

输入:

N M

接下来的N-1行是树的边,和该边的苹果数N and M (1 ≤ M < N; 1 < N ≤ 100)

输出:

剩余苹果的最大数量。

 

input

5 2

1 3 1

1 4 10

2 3 20

3 5 20

output

21

 

算法:

删除了某个分支,那么这个分支下的子分支也同时删除。

保留M个分支,也就是删除N-M-1个分支。剩余的最多苹果数=总苹果数-剪掉的苹果数。

注意本题给的边并没有按照树根--树叶的形式来给,也没有按照树的顺序给出边。本来想一个节点对应一个分支长着的苹果数量,cost[v]就表示v这个节点的苹果数,可以这样做,但是在输入的时候,不知道这个苹果数量是那个节点的,因为不知道哪个是哪个的子结点。所以用了无向图和苹果数加到边上去。

我的解法中:这题的树状DP的整体思想个pku3345是一样的。

有一些不一样的地方要注意一下:

本程序其实不仅仅针对二叉树,可以是任意的树,删除任意个分支都有算。

#include<iostream>
#include<vector>
#include<limits>
using namespace std;
#define MN 110
int f[2*MN],p[MN],tmp[MN];
int N,M;
bool visit[MN];
struct NODE
{
       int val;
       int cost;
};
vector<NODE>G[MN];
inline int max(int a,int b)
{
       return a>b?a:b;
}
inline int min(int a,int b)
{
       return a<b?a:b;
}
void my_clear()
{
       int i;
       for(i=0;i<=N;i++)
       {
              G[i].clear();
       }
       memset(visit,false,sizeof(visit));
}
int DP(int v,int from)
{
       visit[v]=true;
       int i,j,k,s,w,last,now;
       s=G[v].size();
       if(s==1)  //这边不再是s==0
       {
              p[0]=0;
              return 1;
       }
       last=0;
       f[from]=0;
       for(i=0;i<s;i++)
       {
              w=G[v][i].val;
              if(visit[w]==true)
                     continue;
              now=DP(w,from+last+1);
              p[now]=p[now-1]+G[v][i].cost;  //这边不要漏,把节点w也给删除
              for(j=0;j<=last+now;j++)
                     tmp[j]=INT_MAX;
              for(j=0;j<=last;j++)
              {
                     for(k=0;k<=now;k++)
                     {
                            tmp[j+k]=min(tmp[j+k],f[from+j]+p[k]);
                     }
              }
              last+=now;
              for(j=0;j<=last;j++)
              {
                     f[from+j]=tmp[j];
              }
       }
       for(i=0;i<=last;i++)
              p[i]=f[i+from];
       last++; //加上自身节点
       return last;
}
int main()
{
       int i,a,b,sum,c;
       NODE tmp;
       while(scanf("%d%d",&N,&M)!=EOF)
       {
              sum=0;
              my_clear();
              for(i=1;i<N;i++)
              {
                     scanf("%d%d%d",&a,&b,&c);
                     tmp.cost=c;
                     tmp.val=b;
                     G[a].push_back(tmp);
                     tmp.val=a;
                     G[b].push_back(tmp);
                     sum+=c;
              }
              DP(1,0);
              printf("%d\n",sum-f[N-M-1]);
       }
       return 0;
}


 

有限电视网络(pku1155 TELE)

 

题目描述:

有一个电视台要用电视网络转播节目。这种电视网络是一树,树的节点为中转站或者用户。树节点的编号为1~N,其中1为总站,2~(N-M)为中转站,(总站和中转站统称为转发站)N-M+1~N为用户,电视节目从一个地方传到另一个地方都要费用,同时每一个用户愿意出相应的钱来付电视节目。现在的问题是,在电视台不亏本的前提下,要你求最多允许有多少个用户可以看到电视节目。

 

输入:

N M  N表示转发站和用户总数,M为用户数

以下N-M行,第i行第一个K,表示转发站i和K个(转发站或用户)相连, 其后第j对数val,cost表示,第i个转发站到val有边,费用cost.

最后一行M个数表示每个用户愿意负的钱。

输出:

不亏本前提下,可以收到节目最多的用户数。

(如果某个用户要收到节目(叶子结点),那么电视台到该用户的路径节点的费用都要付)

 

Sample Input

 

9 6

3 2 2 3 2 9 3

2 4 2 5 2

3 6 2 7 2 8 2

4 3 3 3 1 1

Sample Output

 

5

 

算法:

这是一道树状态DP.

 

状态f(n,k)表示第n个节点发送给k个用户最多能盈利多少,k不超过n所管辖的叶节点个数。那么答案就是使f(root,k)>=0最大的k了。

通常我们状态转移的时候是枚举每个子状态的,但是这里我们还得用一个DP来枚举子状态。假设一个节点i有n个子节点,那么求f(i,k)时就要考虑怎么把k分到n个节点上使得盈利最大。

下面关键是如何去枚举:

如果节点i有X个子结点设为1~~X,假如当前DP算到第j(0=<j<=X)个节点,第j个节点有now个用户,而0~j-1共有last用户,f[i][k]=max(f[i][k],f[i][a]+f[j][b])(其中a+b=k,0=<k<=last+now);  f[i][a]表示把a个用户分配给0~~j-1节点,分配b个用户给j这个节点。

本程序用写了另外一个用滚动数组来省空间的方法,注意多了一个参数from,否则原来的值会被覆盖掉。

#include<iostream>
#include<limits>
#include<vector>
using namespace std;
int N,M;
vector<int>G[3001];
int money[3001],cost[3001],p[3001],f[6001],tmp[3001];
void my_clear()
{
       int i;
       for(i=0;i<=N;i++)
       {
              G[i].clear();
       }
}
int DP(int v,int from)
{
       int i,s,last,now,k,j,w;
       s=G[v].size();
       if(!s)
       {
              p[0]=0;
              p[1]=money[v-N+M];
              return 1;
       }
       last=0;
       f[from]=0;
       for(i=0;i<s;i++)
       {
              w=G[v][i];
              now=DP(w,from+last+1);
              for(j=0;j<=last+now;j++)
              {
                     tmp[j]=INT_MIN;
              }
              for(j=1;j<=now;j++)
              {
                     p[j]-=cost[w];
              }
              for(j=0;j<=last;j++)
              {
                     for(k=0;k<=now;k++)
                     {
                            if(f[from+j]+p[k]>tmp[j+k])
                            {
                                   tmp[j+k]=f[from+j]+p[k];
                            }
                     }
              }
              tmp[0]=0;
              last+=now;
              for(j=0;j<=last;j++)
              {
                     f[j+from]=tmp[j];
              }
       }
 
       for(j=0;j<=last;j++)
              p[j]=f[j+from];
       return last;
}
int main()
{
       int i,K,j,b;
       scanf("%d%d",&N,&M);
       my_clear();
       for(i=1;i<=N-M;i++)
       {
              scanf("%d",&K);
              for(j=0;j<K;j++)
              {
                     scanf("%d",&b);
                     scanf("%d",&cost[b]);
                     G[i].push_back(b);
              }
       }
       for(i=1;i<=M;i++)
              scanf("%d",&money[i]);
       DP(1,0);
       for(i=M;i>=0;i--)
       {
              if(f[i]>=0)
                     break;
       }
       printf("%d\n",i);
       return 0;
}


 

 

最少步数摘最多的苹果(pku2486  Apple Tree)

Description

Wshxzt is a lovely girl. She likes apple very much. One day HX takes her to an apple tree. There are N nodes in the tree. Each node has an amount of apples. Wshxzt starts her happy trip at one node. She can eat up all the apples in the nodes she reaches. HX is a kind guy. He knows that eating too many can make the lovely girl become fat. So he doesn’t allow Wshxzt to go more than K steps in the tree. It costs one step when she goes from one node to another adjacent node. Wshxzt likes apple very much. So she wants to eat as many as she can. Can you tell how many apples she can eat in at most K steps.

Input

There are several test cases in the input

Each test case contains three parts.

The first part is two numbers N K, whose meanings we have talked about just now. We denote the nodes by 1 2 ... N. Since it is a tree, each node can reach any other in only one route. (1<=N<=100, 0<=K<=200)

The second part contains N integers (All integers are nonnegative and not bigger than 1000). The ith number is the amount of apples in Node i.

The third part contains N-1 line. There are two numbers A,B in each line, meaning that Node A and Node B are adjacent.

Input will be ended by the end of file.

Note: Wshxzt starts at Node 1.

Output

For each test case, output the maximal numbers of apples Wshxzt can eat at a line.

Sample Input

2 1

0 11

1 2

3 2

0 1 2

1 2

1 3

Sample Output

11

2

f[i][j][0]保存对于节点i向其子树走j步(可能有点重复)摘到的最多苹果数

f[i][j][1]保存对于节点i向其子树走j步 并且返回到i节点 摘到的最多苹果数

对于叶节点f[i][0][0/1]=apNum[i];

对于其它节点f[i][j][0]=max{j-2*p-1步,其中p个子树返回,1个子树不需要返回,所得到最多苹果数,p不定};

   f[i][j][1]=max{j-2*p步,p个子树都返回,所得到的最多苹果数,p不定}

   这两步中间过程都需要DP 复杂度=j*子树个数(<n)

那么最终结果就是f[1][k][0]

整个大的DP时间复杂度<n*(k*n)<2*10^6

树型DP中套小DP

*/

#include<iostream>
#include<vector>
using namespace std;
#define MN 101
int back[201][201],go[201][201],num[101],t1[201],t2[201];
vector<int>G[MN];
int N,K;
inline int max(int a,int b)
{
       return a>b?a:b;
}
void my_clear()
{
       int i;
       for(i=0;i<=N;i++)
       {
              G[i].clear();
       }
       memset(go,0,sizeof(go));
       memset(back,0,sizeof(back));
}
void DP(int v,int p)
{
       int i,s,w,j,m,n;
       s=G[v].size();
       for(i=0;i<s;i++)
       {
              w=G[v][i];
              if(w==p)
                     continue;
              DP(w,v);
              back[w][0]=0;
              back[w][1]=0;
              go[w][0]=0;
              for(j=K;j>=2;j--)
              {
                     back[w][j]=back[w][j-2]+num[w];
              }
              for(j=K;j>=1;j--)
              {
                     go[w][j]=go[w][j-1]+num[w];
              }
              memset(t1,0,sizeof(t1));
              memset(t2,0,sizeof(t2));
              for(m=0;m<=K;m++)
              {
                     for(n=0;n<=m;n++)
                     {
                            t1[m]=max(t1[m],back[v][n]+back[w][m-n]);
                            t2[m]=max(t2[m],max(go[v][n]+back[w][m-n],back[v][n]+go[w][m-n]));
                     }
              }
              for(j=0;j<=K;j++)
              {
                     back[v][j]=t1[j];
                     go[v][j]=t2[j];
              }
       }
}
int main()
{
       int a,b;
       while(scanf("%d%d",&N,&K)!=EOF)
       {
              int i;
              for(i=1;i<=N;i++)
                     scanf("%d",&num[i]);
              my_clear();
              for(i=1;i<N;i++)
              {
                     scanf("%d%d",&a,&b);
                     G[a].push_back(b);
                     G[b].push_back(a);
              }
              DP(1,0);
              int ans=max(go[1][K],back[1][K]);
              ans+=num[1];
              printf("%d\n",ans);
       }
       return 0;
}


 

钻石总统(pku3345  Bribing FIPA)

 

题目大意:

有个人要竞选某职务,有N个国家参选,他想通过贿赂某些国家来赢得这个国家的选票,每个国家都要花费相应的钻石才可以被贿赂。因为国家与国家之间存在着控制关系,付费给某个国家,那么就可以赢得这个国家和它直接或间接控制的所有国家的选票。

其实国家的控制关系可以看成一棵树,对于本题,是给一个森林。

你的任务是如果至少要赢得M个国家的选票,最少要花多少钻石。

输入:

3 2    //N ,M 接下来是N行,每行前两个为国家名和贿赂改国家需要的钻石数,后面跟着的是这个国家可以控制的国家名字,可以没有。

Aland 10  

Boland 20 Aland

Coland 15

#

输出:

最后需要的钻石数。

输入处理用了sstream ,真的很好用。不然可能很不好处理。

这题要注意:输入不会有环。但是给你的数可能不联通,所以加一个根节点吧所有的树都连成一棵树,然后在书上进行DP.

DP的方法和1155的方法类似,也用了from参数。

 

#include<iostream>
#include<vector>
#include<map>
#include<limits>
#include<sstream>
using namespace std;
map<string,int>mp;
#define MN 210
vector<int>G[MN];
int f[2*MN],p[MN],tmp[MN],cost[MN],in[MN];
int N,M;
char chartmp[1000];
inline int max(int a,int b)
{
       return a>b?a:b;
}
inline int min(int a,int b)
{
       return a<b?a:b;
}
void my_clear()
{
       int i;
       for(i=0;i<=N;i++)
       {
              G[i].clear();
       }
       memset(in,0,sizeof(in));
       mp.clear();
}
int DP(int v,int from)
{
       int i,j,k,s,w,last,now;
       s=G[v].size();
       if(s==0)
       {
              p[0]=0;
              p[1]=cost[v];
              return 1;
       }
       last=0;
       f[from]=0;
       for(i=0;i<s;i++)
       {
              w=G[v][i];
              now=DP(w,from+last+1);
              for(j=0;j<=last+now;j++)
                     tmp[j]=INT_MAX;
              for(j=0;j<=last;j++)
              {
                     for(k=0;k<=now;k++)
                     {
                            tmp[j+k]=min(tmp[j+k],f[from+j]+p[k]);
                     }
              }
              last+=now;
              for(j=0;j<=last;j++)
              {
                     f[from+j]=tmp[j];
              }
       }
       f[from+last+1]=cost[v];
       last++;
       for(i=0;i<=last;i++)
              p[i]=f[i+from];
       return last;
}
int main()
{
       int i,root,a,b,id;
       string t;
       while(gets(chartmp))
       {
              if(chartmp[0]==0)
                     continue;
              if(chartmp[0]=='#')
                     break;
              stringstream ss(chartmp);
              ss>>N>>M;
              id=1;
              my_clear();
              for(i=1;i<=N;i++)
              {
                     scanf("%s",chartmp);
                     if((a=mp[chartmp])==0)
                     {
                            a=mp[chartmp]=id++;
                     }
                     scanf("%d",&cost[a]);
                     gets(chartmp);
                     stringstream ss(chartmp);
                     while(ss>>t)
                     {
                            if((b=mp[t])==0)
                            {
                                   b=mp[t]=id++;
                            }
                            in[b]++;    //
                            G[a].push_back(b);//这两个是放在if外,开始的时候错了
                     }
              }
              for(i=1;i<=N;i++)
              {
                     if(in[i]==0)
                     {
                            G[0].push_back(i);
                     }
              }
              cost[0]=0;
              int tt=DP(0,0);
              int ans=INT_MAX;
              for(i=M;i<=N;i++)
              {
                     if(ans>f[i])
                     {
                            ans=f[i];
                     }
              }
              printf("%d\n",ans);
       }
       return 0;
}


 

删除最少的边使得树剩下的给定数目节点(pku1947 Rebuilding Roads)

 

题目的意思:

给你一棵树,求通过求通过删除最少的边,使得剩余子树有给定的节点数。

输入:

N ,P //N为节点总数,P为删除边后剩余的节点树

以下 N-1行给出树的边。

题目没说清楚给出的边是否是有向的。

对于本题可以按照有向树来做,其实无向树来做更保险,应用性更广。看成有向树,对于本题树根就是1,题目没说,判断一下更保险,如果是看成无向树,则无所谓谁为根节点。

开始的时候,对于本题一直按照以往的树形DP的方法来做,一直错。查了好久才查出哪里错了,因为看到别人的程序的一个地方,多了一段程序,删除这一部分则是错误的。

     ans = f[root][p];

     for (i = 1; i <= n; i++)  //这个for循环是必须的。

     {

         if (f[i][p] < ans) ans = f[i][p]+1;  //这边之所以要加一因为要删除i节点和其父亲节点的连接

     }

后来我就在DP的函数末尾,多了一个判断。

按照以往的DP方法,一下这个例子是错的,输出的是3,而答案是1

12 7

1 11 1 12 1 2 2 3  2  4   4  5 4   8 5  6 5 7 8  9 8 10

如果看成有向树,1为根,以1为根进行DP的最后的答案是3,原因是这样DP的所有结果(即剩余的P个节点)都包含根节点1,而对于上面一个数据,一最后的节点不包含根根节点,而是删除一个边后,边下面的子树保留,上面的部分(包括根节点全部删除),所以就多了判断那一部分。即在DP的子问题中判断是否有更优解。

#include<iostream>
#include<vector>
using namespace std;
#define MN 160
vector<int>G[MN];
int f[2*MN],p[MN],tmp[MN],N,P,in[MN],root;
int ans;
void my_clear()
{
       int i;
       for(i=0;i<=N;i++)
       {
              G[i].clear();
       }
}
inline int min(int a,int b)
{
       return a<b?a:b;
}
int DP(int v,int from)
{
       int last,now,w,i,s,j,k;
       s=G[v].size();
       last=0;
       f[from]=0;
       for(i=0;i<s;i++)
       {
              w=G[v][i];
              now=DP(w,last+from+1);
              for(j=0;j<=last+now;j++)
              {
                     tmp[j]=INT_MAX;
              }
              for(j=0;j<=last;j++)
              {
                     for(k=0;k<=now;k++)
                     {
                            tmp[j+k]=min(tmp[j+k],f[j+from]+p[k]);
                     }
              }
              last+=now;
              for(j=0;j<=last;j++)
              {
                     f[j+from]=tmp[j];
              }
       }
       p[0]=0;
       for(j=0;j<=last;j++)
              p[j]=f[j+from];
       last++;
       p[last]=0;
       if(last>=P&&v!=root&&p[last-P]+1<ans)
       {
              ans=p[last-P]+1;
       }
       p[last]=1;
       return last;
}
int main()
{
       int i,a,b;
       while(scanf("%d%d",&N,&P)!=EOF)
       {
              my_clear();
              ans=INT_MAX;
              memset(in,0,sizeof(in));
              for(i=1;i<N;i++)
              { //对于本题可以按照有向树来做,其实无向树来做更保险,应用性更广。看成有向树,对于本题树根就是1,
                     scanf("%d%d",&a,&b);   //为了保险,还是要判断。
                     G[a].push_back(b);
                     in[b]++;
              }
              for(i=1;i<=N;i++)
                     if(!in[i]){
                            root=i;
                            break;
                     }
              DP(root,0);
              if(f[N-P]<ans)
                     ans=f[N-P];
              printf("%d\n",ans);
       }
       return 0;
}


 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值