二叉苹果树
题目意思:
有一棵苹果树,苹果树的是一棵二叉树,共N个节点,树节点编号为1~N,编号为1的节点为树根,边可理解为树的分枝,每个分支都长着若干个苹果,现在要要求减去若干个分支,保留M个分支,要求这M个分支的苹果数量最多。
输入:
N M
接下来的N-1行是树的边,和该边的苹果数N and M (1 ≤ M < N;1 < N ≤ 100)
输出:
剩余苹果的最大数量。
实现一:
【算法&思路】:首先,可以肯定的是,这是一道有关树规的题目,父节点和子节点存在着相互关联的阶段关系。
第一步完成。再执行第二步:我们观察到题目数据量不大,所以有两种选择:邻接矩阵和邻接表。因为邻接矩阵的代码简单,思路清晰,所以建议能写邻接矩阵的时候就不要写邻接表了。我们设ma[x][y]为边的值,因为树是双向的,所以要再记录ma[y][x]。
设tree[v,1]为节点v的左子树,tree[v,2]为节点v的右子树,然后我们再递归建树(因为树是递归定义的,所以很多时候建树都要考虑递归)。
建树的问题解决的了,我们就要列状态转移方程了。根据求什么设什么的原则,我们定义f[i][j]表示以i为节点的根保留k条边的最大值,那么 f[v][k]=max(f[v][k],(f[tree[v][1]][i]+f[tree[v][2]][k-i-1]+num[v])),我们枚举 i就可以了。正如我开头提到的。因为树是递归定义的所以我们可以用记忆化搜索的形式(dfs)来具体实现。而树本身严格分层,而且没有环。所以是不会重复 的。
F[1][Q+1]就是答案。因为题目中给的是边的权值,而我们在处理时将每条边的权值全赋给其所连的父节点和子节点中的子节点(将关于边的问题转化为关于点的问题),所以最后是Q+1,表示点的数目。
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<memory>
#include<algorithm>
#include<string>
#include<climits>
#include<queue>
#include<vector>
#include<cstdlib>
#include<map>
using namespace std;
const int ee=105;
int n,q;
int tree[ee][5]={0},ma[ee][ee]={0},num[ee]={0},f[ee][ee]={0};
void preproccess()
{
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
{
ma[i][j]=-1;
ma[j][i]=-1;
}
}
void maketree(int v);
void build(int x,int y,int lor)//lor means left or right
{
num[y]=ma[x][y];
tree[x][lor]=y;
ma[x][y]=-1;ma[y][x]=-1;
maketree(y);
}
void maketree(int v)
{
int lr=0;
for(int i=0;i<=n;i++)
if(ma[v][i]>=0)//如果分叉了,那么记录
{
lr++;//1 or 2 表示左支还是右支;
build(v,i,lr);//存入并递归
if(lr==2) return;
}
}
void dfs(int v,int k)
{
if(k==0) f[v][k]=0;
else if(tree[v][1]==0 && tree[v][2]==0) f[v][k]=num[v];
else
{
f[v][k]=0;
for(int i=0;i<k;i++)
{
if(f[tree[v][1]][i]==0) dfs(tree[v][1],i);
if(f[tree[v][2]][k-i-1]==0) dfs(tree[v][2],k-i-1);
f[v][k]=max(f[v][k],(f[tree[v][1]][i]+f[tree[v][2]][k-i-1]+num[v]));
}
}
}
int main()
{
//freopen("in.txt","r",stdin);
cin>>n>>q;
preproccess();
for(int i=0;i<n;i++)
{
int x,y,xy;
scanf("%d%d%d",&x,&y,&xy);
ma[x][y]=xy;
ma[y][x]=xy;
}
//建树;
maketree(1);
dfs(1,q+1);
cout<<f[1][q+1];
fclose(stdin);fclose(stdout);
return 0;
}
实现二:
思路:由于是一颗二叉树,所以可以用树形DP,对于每一点u寻找u的子树,定义dp[u][j]表示在以u为根的子树保留j个分支可以得到的最大苹果数量
最后答案输出dp[1][m]即可
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <queue>
#include <algorithm>
#include <map>
#include <iomanip>
#define INF 99999999
typedef long long LL;
using namespace std;
const int MAX=100+10;
int head[MAX],size,n,m;
int dp[MAX][MAX];
struct Edge{
int v,w,next;
Edge(){}
Edge(int V,int W,int NEXT):v(V),w(W),next(NEXT){}
} edge[MAX*2];
void Init(int num){
for(int i=0;i<=num;++i)head[i]=-1;
memset(dp,0,sizeof dp);
size=0;
}
void InsertEdge(int u,int v,int w){
edge[size]=Edge(v,w,head[u]);
head[u]=size++;
}
int dfs(int u,int father){
int ans=0;//u的子树有多少分支
for(int i=head[u];i != -1;i=edge[i].next){
int v=edge[i].v,w=edge[i].w;
if(v == father)continue;
ans+=dfs(v,u)+1;
for(int j=min(ans,m);j>=1;--j){
for(int k=min(j,ans);k>=1;--k){
dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k-1]+w);
}
}
}
return ans;
}
int main(){
int u,v,w;
while(cin>>n>>m){
Init(n);
for(int i=1;i<n;++i){
cin>>u>>v>>w;
InsertEdge(u,v,w);
InsertEdge(v,u,w);
}
dfs(1,-1);
cout<<dp[1][m]<<endl;
}
return 0;
}

浙公网安备 33010602011771号