今天晚上我做了几题树形dp的题 给大家露一手
题意:
一棵无向树,结点为n(<=10,000),删除哪些结点可以使得新图中每一棵树结点小于n/2。
这不是一道搜索题!这不是一道搜索题!这不是一道搜索题!
就是枚举每个点删去后的最大值。
判断最大值的要求 1.childmax<n/2 最大的子树满足条件 2.n-sum <= n/2 这个节点之前的树也满足条件
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 10010;
vector<int>g[maxn];
int fa[maxn],ans[maxn];
int n,num=0;
int dfs (int root ,int father)//dfs 找到叶节点向上回溯
{
int sum=1, childmax=0;//sum记录这颗子树的节点数
for(int i=0;i<g[root].size();i++)
{
if(g[root][i]!=father)//父节点就跳过,不在找一遍
{
int son_sum= dfs(g[root][i],root);//其root的子树的总值
childmax= max(childmax,son_sum);// 找最大的 子数的节点数
sum += son_sum; //总子节点+本身
}
}
childmax = max(childmax,n-sum);//最大子树和这个点之前的树
if(childmax<=n/2)
{
ans[num++]=root;
}
return sum;//返回这个子树的总值
}
int main(){
int x,y;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,0);//从1 开始dp
if(num){
//按顺序输出
sort(ans,ans+num);
for(int i= 0;i<num;i++)
printf("%d\n",ans[i]);
}
else {
printf("NONE\n");
}
}
现在要在一棵树上布置士兵,每个士兵在结点上,每个士兵可以守护其结点直接相连的全部边,问最少需要布置多少个士兵。
此题是一道经典的树形dp,每个点覆盖的是相邻的边,如果你会匈牙利算法也可以写(反正我不会)
定义状态dp[u][0/1]表示u这个节点不放/放士兵
对于节点u,如果这个点放士兵,则其子节点可以放也可以不放 dp[u][1]+= min(dp[v][1],dp[v][0]) v为u的子节点
如果u不放士兵其子节点就一定要放士兵 dp[u][0] += dp[v][1]
因为树形dp自下而上的查找,只要考虑其子节点对其的影响即可。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1550;
vector<int>g[maxn];
int dp[maxn][2];
int fa[maxn];
void dfs (int root){
for(int i=0;i<g[root].size();i++){
dfs(g[root][i]);//dfs 到叶节点
}
for(int i=0 ;i<g[root].size();i++){
//这棵树的最小节点
dp[root][0] += dp[g[root][i]][1];
dp[root][1] += min(dp[g[root][i]][0],dp[g[root][i]][1]);
}
}
int main()
{
int n;
int x,k,ss;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
g[i].clear();
dp[i][1]=1;dp[i][0]= 0;//dp[i][1]取这个点
fa[i]=-1;
}
for(int i=0;i<n;i++)
{
scanf("%d%d",&x,&k);
for(int j=0;j<k;j++)
{
scanf("%d",&ss);
g[x].push_back(ss);//放入其子树
fa[ss]=x;
}
}
int root = 1;
while(fa[root]!=-1)root = fa[root]; //找一个根节点
dfs(root);
printf("%d\n",min(dp[root][0],dp[root][1]));
return 0;
}
题意
给一一颗树,问删除哪些结点可以使得剩下的子图的结点数<=总/2。
这个和上一个有点像,但是是覆盖点,不是覆盖边,
定义dp[u][0/1/2]
dp[u][0]: u不放士兵,父节点覆盖
dp[u][1]: u不放士兵,子节点覆盖
dp[u][2]: u放士兵
dp[u][0] += min (dp[v][1],dp[v][2]);
dp[u][2] +=min(dp[v][1],dp[v][2],dp[v][0])+1;
dp[u][1] =min(dp[v][1],dp[v][2])
子节点v不存在被染色的父亲;若所有v均不染色,此时u未被覆盖,故需要有一个v来染色,选择min(dp[v][2]-dp[v][1])即可。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int Inf = 0x3f3f3f3f;
const int maxn = 10010;
vector<int>g[maxn];
int dp[maxn][3]={0};
int fa[maxn];
void dfs (int root,int father){
if(g[root].size() == 1&& g[root][0]==father){
dp[root][0]=0;
dp[root][1]=Inf;
dp[root][2]=1;
return ;
}
int mini= Inf,flag=1;
for(int i=0;i<g[root].size();i++)
{
int v =g[root][i];
if(v==father)continue;
dfs(v,root);
dp[root][0] += min(dp[v][1],dp[v][2]);
dp[root][2] += min(min(dp[v][1],dp[v][2]),dp[v][0]);
if(dp[v][1] < dp[v][2])
{
//
dp[root][1] += dp[v][1];
mini = min(mini, (dp[v][2] - dp[v][1]));
}
else{
flag = 0;
dp[root][1] += dp[v][2];
}
}
dp[root][2]++; //这个点要加1;
//如果所有子节点都没有覆盖root就放最小的点
if(flag) dp[root][1] += mini;
}
int main()
{
int n;
int x,y;
scanf("%d",&n);
for(int i=0;i<=n;i++)g[i].clear();
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
g[x].push_back(y);
g[y].push_back(x);
}
dfs(1,0);
printf("%d\n",min(dp[1][1],dp[1][2]));
return 0;
}
题意:一棵边带权值的树,求每个点在树上的最远距离。
有点像上一题,这个是带全值的树
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 10010;
struct edge{
int next,w;
};
vector<edge>g[maxn];
int id[maxn];
int dp[maxn][3];
void dfs (int root ,int father)
{
for(int i=0;i<g[root].size();i++)
{
int next = g[root][i].next,w=g[root][i].w;
if(next == father)continue;
dfs(next,root);
//子树的max
if(dp[root][0]<dp[next][0]+w)
{
dp[root][0]= dp[next][0]+w;
id[root] = next;
}
}
for(int i=0;i<g[root].size();i++)
{
int next = g[root][i].next,w=g[root][i].w;
//子树的次max
if(next == father||next==id[root])continue;
dfs(next,root);
dp[root][1]=max(dp[root][1],w+dp[next][0]);
}
}
void dfs2(int root,int father)
{
for(int i=0;i<g[root].size();i++)
{
int next = g[root][i].next,w=g[root][i].w;
if(next == father)continue;
if(next==id[root])//在最长就取次长
{
dp[next][2] = max(dp[root][2],dp[root][1]) + w ;
}
else {//取最长
dp[next][2]= max(dp[root][2],dp[root][0]) + w;
}
dfs2(next,root);
}
}
int main(){
int n;
int father,w;
while(~scanf("%d",&n))
{
for(int i =1;i<=n;i++)
{
g[i].clear();
}
memset(id,0,sizeof id);
memset(dp,0,sizeof dp);
for(int i=2;i<=n;i++)
{
scanf("%d%d",&father,&w);
edge ss,tt;
ss.w=w;tt.w=w;
ss.next= father,tt.next=i;
g[father].push_back(tt);
g[i].push_back(ss);
}
dfs(1,-1);
dfs2(1,-1);
for(int i=1;i<=n;i++)
{
printf("%d\n",max(dp[i][2],dp[i][0]));
}
}
}