引入
顾名思义,在树上进行动态规划,所以首先要满足,是一棵树,然后观察是否满足DP的要求。我们来具体情况具体分析。
例题
题目大意:在一棵树上安插士兵,在节点上的一个士兵能看到所有与它相连的边,求最少安排的士兵能看到所有的路。
满足是一棵树,而是求最小值。我们考虑用树形DP,对于每一个节点,只有2种状态,放与不放,那么我们用二维存储dp[i][j]表示i节点的状态1或0表示总的士兵数。
状态转移:
若该节点不放,那么它的子节点必须放。
若该节点放,那么它的子节点可放可不放,取最小值即可。
由于是一棵树,根节点没有父节点,所以可以从根开始向叶子节点遍历,回溯回来的到根节点即是答案。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,dp[N][2];//只有2种状态
bool croot[N];
vector<int>a[N];//向量存储邻接表
void dfs(int root)
{
dp[root][0]=0;//不放,没士兵
dp[root][1]=1;//放,有一个士兵
for(int i=0;i<a[root].size();i++)//子节点
{
int y=a[root][i];
dfs(y);
dp[root][0]+=dp[y][1]; //父节点不放,子节点必须放
dp[root][1]+=min(dp[y][0],dp[y][1]);//父节点放,子节点可放可不放
}
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
a[i].clear();
}
memset(croot,1,sizeof(croot));
int x,y,m;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&x,&m);
for(int j=1;j<=m;j++)
{
scanf("%d",&y);
a[x].push_back(y);//只从根向下找
croot[y]=0; //找根,看是否有入度
}
}
int root;
for(int i=0;i<n;i++)
if(croot[i]==1)
{
root=i;//找到了根
break;
}
dfs(root);//遍历树
cout<<min(dp[root][1],dp[root][0]);//输出放还是不放
return 0;
}
送一道类似的题目luoguP1352
只需要将要参加时,该节点快乐为0,不参加快乐为a[i]
深入
传送门P2015
因为是一个二叉树,我们可以用l和r数组来存储左右儿子,
我们先把边上的苹果转到节点上,
那么状态转移就是左二子保留的节点数+右二子保留的节点数+此节点苹果
其中保留多少就通过枚举,然后不断更新答案,
如果有节点已经搜过,即dp值被改过,直接return,即记忆化搜索
#include<bits/stdc++.h>
using namespace std;
int n,q,l[105],r[105],a[105];
int dp[105][105];
int Map[105][105];
void MakeTree(int v)
{
for(int i=1;i<=n;i++) //建左子树
if(Map[v][i]>=0)
{
l[v]=i;
a[i]=Map[v][i]; //把边上苹果转移到点i上
Map[v][i]=-1; //边上没有苹果了
Map[i][v]=-1;
MakeTree(i);
break;
}
for(int i=1;i<=n;i++) //建右子树
if(Map[v][i]>=0)
{
r[v]=i;
a[i]=Map[v][i];
Map[v][i]=-1;
Map[i][v]=-1;
MakeTree(i);
break;
}
}
int DP(int i,int j) //树型DP,记忆化搜索
{
if(j==0) return 0;
if((l[i]==0)&&(r[i]==0)) return a[i]; //如果i是叶子节点
if(dp[i][j]>0) return dp[i][j]; //如果f[i][j]已经计算直接返回值
for(int k=0;k<=j-1;k++)
dp[i][j]=max(dp[i][j],DP(l[i],k)+DP(r[i],j-k-1)+a[i]);
return dp[i][j];
}
int main()
{
int x,y,z;
scanf("%d",&n,&q);
q++;
memset(Map,-1,sizeof(Map));
for(int i=1;i<=n-1;i++)
{
scanf("%d%d%d",&x,&y,&z);
Map[x][y]=z;
Map[y][x]=z;
}
MakeTree(1); //以1为根建立二叉树
cout<<DP(1,q)<<endl;
return 0;
}
qwq