搜集钻石
Description
蒜国有 n 座城市,编号从 1 到 n,城市间有 n−1 条道路,且保证任意两座城市之间是连通的。每一座城市有一定数量的钻石。
蒜头君想在蒜国搜集钻石。他从城市 1 出发,每天他可以通过城市之间道路开车到另外的城市。当蒜头第一次到一个城市的时候,他可以搜集完这个城市的所有钻石,如果他后面再来到这个城市,就没有砖石可以收集了。
蒜头君只有 K 天时间,请你帮算蒜头君计算他最多可以搜集多少钻石。
Input
第一行输入三个整数 n(1≤n≤100),K(0≤k≤200),表示一共有 n 座城市,蒜头君有 K 天时间。
接下里一行输入 n 个用空格隔开的整数,表示每个城市的钻石数量。每个城市的钻石数量不大于 1000。
接下来输入 n−1 行,每行输入两个整数 a(1≤a≤n),b(1≤b≤n),表示城市 a和城市 b 之间存在一条双向道路。
Output
输出一行一个整数表示蒜头君最大能获取的钻石数量。
Sample Input 1
3 2
3 8 3
1 3
3 2
Sample Output 1
14
Sample Input 2
6 2
5 9 8 4 9 2
1 6
6 2
2 5
5 3
5 4
Sample Output 2
16
Source
计蒜客
题意分析:有n个节点,n-1条边,联通,很容易就可以想到是一棵树,又让求最大值,自然而然的就联想到了树形DP;则原题就变成了——给你一棵树,每条边(双向)的权值为一,每个节点有一定价值且只能取一次(注意去了一定还要回来才可以到同层的另一子树),有看k天,问能取到的最大价值;
题目分析:对于每个子树,它的子树可以去到不止一个(在时间够的情况下),且对于每棵子树有两种操作——去了回来和去了不会;去了回来的情况比较好考虑——如果根节点去了要回,则它的每个子树去了都要回,于是很容易就可以敲出一个树上的分组背包(具体操作不赘述);但麻烦的在不回来的情况——可以有很多个回来的字树,但只能有一个不回来的子树,根据惯性思维,在想出了多重背包解回来情况之后,便会在想到确定一个不回来的子树,再在剩下的子树中做一次分组背包,取最大值,我就收这种垃圾思维的影响苦苦肝了很久(欲哭无泪),接下来先送上我的70分代码(其实细节处理好了应该可以满分):
#include<bits/stdc++.h>
using namespace std;
int n,m,ma[1100],dp[1100][2100][2];
vector<int> vec[1100];
void dfs(int a,int b,int c,int fa)
{//cout<<a<<" "<<b<<" "<<c<<" "<<fa<<endl;
int l=vec[a].size();
if(b==0||l==1&&a!=1)//边界值
{
for(int i=0;i<=b;i++)
{
dp[a][i][c]=ma[a];
}
//cout<<a<<" "<<b<<" "<<c<<" "<<dp[a][b][c]<<endl;
}
else if(c==1)//要回来
{
int tmp=b-b%2,dp2[2100];
memset(dp2,0,sizeof(dp2));
for(int i=0;i<l;i++)//分组背包
{
if(vec[a][i]==fa) continue;
dfs(vec[a][i],tmp-2,1,a);
for(int j=tmp-2;j>=2;j-=2)
{
for(int e=0;e<=j;e+=2)
{
dp2[j]=max(dp2[j],dp2[j-e]+dp[vec[a][i]][e][1]);
dp2[j+1]=dp2[j];
//cout<<j<<dp2[j]<<endl;
}
}
}
for(int i=2;i<=b;i++)
{
dp[a][i][1]=dp2[i-2]+ma[a];
//cout<<a<<" "<<i<<" "<<c<<" "<<dp[a][i][c]<<endl;
}
dp[a][0][1]=ma[a];
dp[a][1][1]=ma[a];
//cout<<a<<" "<<ma[a]<<endl;
}
else//不回来
{
dp[a][0][0]=ma[a];
for(int k=0;k<l;k++)
{
if(vec[a][k]==fa) continue;
dfs(vec[a][k],b-1,0,a);
int tmp=b-b%2,dp2[2100];
memset(dp2,0,sizeof(dp2));
for(int i=0;i<l;i++)
{
if(vec[a][i]==fa||i==k) continue;
for(int j=tmp-2;j>=2;j-=2)
{
for(int e=0;e<=j;e+=2)
{
dp2[j]=max(dp2[j],dp2[j-e]+dp[vec[a][i]][e][1]);
dp2[j+1]=dp2[j];
}
}
}
for(int i=1;i<=b;i++)
{
for(int j=1;j<=i-1;j++)
{//cout<<a<<" "<<i<<" "<<c<<" "<<dp[a][i][c]<<" "<<dp[vec[a][k]][j][0]<<" "<<dp2[i-j]<<" "<<ma[a]<<endl;
dp[a][i][0]=max(dp[a][i][0],dp[vec[a][k]][j-1][0]+dp2[i-j-2]+ma[a]);
}
}
}
//
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>ma[i];
}
for(int i=1;i<n;i++)
{
int a,b;
cin>>a>>b;
vec[a].push_back(b);
vec[b].push_back(a);
}
dfs(1,m,1,-2);
dfs(1,m,0,-2);
cout<<dp[1][m][0];
}
有点长(还没过......);
接下来说一说正解思路(很简洁):首先当然要赋初值dp[u][a][b]统统赋成u节点的值,然后开始枚举子节点(注意不要枚举到父节点了),再递归到下一层的dp[v][a][b](没有它解决不了大问题,具体过程不要深想),再枚举0->m(m为最大天数),再枚举给前面已打出的量的天数e(0->j-1),前面已打出的和当前的又只能有一个去了不回来,于是不难列出方程:要回来的情况:dp[u][j][1]=max(dp[u][j][1],dp[u][e][1]+dp[v][j-e-2][1]); 不回来的情况:dp[u][j][0]=max(dp[u][j][0],dp[u][e][0]+dp[v][j-e-2][1]);dp[u][j][0]=max(dp[u][j][0],dp[u][e][1]+dp[v][j-e-1][0]);
AC代码:
#include<bits/stdc++.h>
using namespace std;
int n,m,dp[210][210][2],ma[210];
vector<int> vec[210];
void dfs(int fa,int u)
{
int l=vec[u].size();
for(int i=0;i<=m;i++)
{
dp[u][i][1]=dp[u][i][0]=ma[u];
}
for(int i=0;i<l;i++)
{
int v=vec[u][i];
if(v==fa) continue;
dfs(u,v);
for(int j=m;j>=1;j--)
{
for(int e=0;e<=j-1;e++)
{
if(j-e>=2)
{
dp[u][j][1]=max(dp[u][j][1],dp[u][e][1]+dp[v][j-e-2][1]);
dp[u][j][0]=max(dp[u][j][0],dp[u][e][0]+dp[v][j-e-2][1]);//前面的不回来
}
dp[u][j][0]=max(dp[u][j][0],dp[u][e][1]+dp[v][j-e-1][0]);//后面的不回来
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>ma[i];
}
for(int i=1;i<n;i++)
{
int a,b;
cin>>a>>b;
vec[a].push_back(b);
vec[b].push_back(a);
}
dfs(-1,1);
cout<<dp[1][m][0];
}