【序言】树形DP一直不太会。趁着练DP的时候,写了两道树形的背包。鉴于这块知识非常不熟练,网上参考了一点题解。为了加强记忆,特写此题解。
【题目1】
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 3445 | Accepted: 1781 |
Description
Input
Output
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
Source
【大意】一棵树中有N个节点,编号是最后M个的节点是叶节点。每条边会有一个花费。你从1号点(根节点)开始,如果到达某个叶节点,你就能获得它的权值,但要付出所经过的边的花费。在你获得的利润S>=0的情况下,要求所到达的叶节点尽量的多。
【分析】用f[i][j]表示到i节点,以i为根的子树中到达j个的最大利润。输出时倒着循环枚举,如果f[1][ans]大于等于0就可行。下面研究状态转移方程。以前我一直以为这种选择最优解的题目要把多叉树转化为二叉树,后来发现其实并不用。我们依次枚举每一个孩子。f[k][now]=max(f[k][now],f[k][now-cut]+f[go][cut]-a[i].s);其中now是枚举当前我所在的根的状态,而cut则是给某个孩子的j值。而a[i].s就是边权。
【注意】①点多,建议开边表。
②运算时可能会有负数,一开始初始化时要赋成负无穷大。
【代码】
#include<stdio.h>
#include<iostream>
using namespace std;
const int maxn=3500+5;
const int INF=2100000000;
struct arr{int r,s,next;}a[maxn];
int num[maxn],ok[maxn],begin[maxn],end[maxn],f[maxn][maxn];
int n,m,x,b,c,i,j,ans,cnt;
void make_up(int u,int v,int s)
{
a[++cnt].r=v;a[cnt].s=s;
if (begin[u]==0) {begin[u]=cnt;end[u]=cnt;}
else {a[end[u]].next=cnt;end[u]=cnt;}
}
void dp(int k)
{
if (k>=n-m+1)
{
f[k][1]=num[k];ok[k]=1;
return;
}
int i=begin[k];
while (i)
{
int go=a[i].r;
dp(go);
ok[k]+=ok[go];
for (int now=ok[k];now>0;now--)
for (int cut=0;cut<=ok[go];cut++)
f[k][now]=max(f[k][now],f[k][now-cut]+f[go][cut]-a[i].s);
i=a[i].next;
}
}
int main()
{
scanf("%ld%ld",&n,&m);
for (i=1;i<=n;i++)
for (j=1;j<=m;j++)
f[i][j]=-INF;
for (i=1;i<=n-m;i++)
{
scanf("%ld",&x);
while (x)
{
scanf("%ld%ld",&b,&c);
make_up(i,b,c);x--;
}
}
for (i=n-m+1;i<=n;i++)
scanf("%ld",&num[i]);
dp(1);
for (ans=m;ans>=0;ans--)
if (f[1][ans]>=0) {printf("%ld",ans);break;}
return 0;
}
Time Limit: 1000MS | Memory Limit: 65536K | |
Total Submissions: 6762 | Accepted: 2242 |
Description
Input
Output
Sample Input
2 1 0 11 1 2 3 2 0 1 2 1 2 1 3
Sample Output
11 2
Source
【分析】原来以为很水,用f[i][j]表示到第i个节点,走了j步的最大值。但是很快就发现这样有点问题。往上一看,原来还要再加一维,f[i][j][0]表示必须最后回到根节点,f[i][j][1]表示不必(当然也可以)回到根节点。
方程似乎很麻烦。
①0的状态:下去再上来,走两步。f[k][run+2][0]=max(f[k][run+2][0],f[go][down][0]+f[k][run-down][0]);
②1的状态
1、直接下去。f[k][run+1][1]=max(f[k][run+1][1],f[go][down][1]+f[k][run-down][0]);
2、也可以再上来。f[k][run+2][1]=max(f[k][run+2][1],f[go][down][0]+f[k][run-down][1]);
【代码】
#include<stdio.h>
#include<iostream>
#include<cstring>
using namespace std;
const int maxn=100+5;
int num[maxn],map[maxn][maxn],data[maxn],f[maxn][maxn*2][2];
bool visit[maxn];
int n,i,step,x,y;
void dp(int k)
{
visit[k]=true;
int i;
for (i=0;i<=step;i++) f[k][i][0]=data[k];
for (i=0;i<=step;i++) f[k][i][1]=data[k];
for (i=1;i<=num[k];i++)
{
int go=map[k][i];
if (visit[go]) continue;
dp(go);
for (int run=step;run>=0;run--)
for (int down=0;down<=run;down++)
{
f[k][run+2][0]=max(f[k][run+2][0],f[go][down][0]+f[k][run-down][0]);
f[k][run+1][1]=max(f[k][run+1][1],f[go][down][1]+f[k][run-down][0]);
f[k][run+2][1]=max(f[k][run+2][1],f[go][down][0]+f[k][run-down][1]);
}
}
}
int main()
{
while (scanf("%ld%ld",&n,&step)!=EOF)
{
memset(num,0,sizeof(num));
memset(map,0,sizeof(map));
memset(f,0,sizeof(f));
memset(visit,0,sizeof(visit));
for (i=1;i<=n;i++) scanf("%ld",&data[i]);
for (i=1;i<n;i++)
{
scanf("%ld%ld",&x,&y);
map[x][++num[x]]=y;map[y][++num[y]]=x;
}
dp(1);
printf("%ld\n",f[1][step][1]);
}
return 0;
}
【后记】细节坑死人!第二个程序原来DP的第二重循环down的终值我原来写成step了,就一直过不了~~