树形DP

 树的最长路径【树形DP】  

      题目 树的最长路径【树形DP】icon-default.png?t=M85Bhttps://www.acwing.com/problem/content/description/1074/

解析 icon-default.png?t=M85Bhttps://www.acwing.com/file_system/file/content/whole/index/content/2826773/

 

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=2*N;
int e[M],ne[M],h[N],idx,w[M];
int f1[N],f2[N];  //用来记录各点路径最大值和次大值
int res;
void add(int a,int b,int c)//邻接表的方式存树
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u,int f)//该节点和该节点父节点
{
    f1[u]=0,f2[u]=0;   //用来记录最大值和次大值
    for(int i=h[u];~i;i=ne[i])//枚举一遍子树
    {
        int j=e[i];
        if(j==f)continue;//防止循环遍历
        dfs(j,u);//该子树到u
        int m=f1[j]+w[i];//该子树到u的距离
        if(m>=f1[u])f2[u]=f1[u],f1[u]=m;//假如大于最大值,更新一遍最大值和次大值
        else if(m>f2[u]) f2[u]=m;//假如大于次大值,则更新次大值
    }
    res=max(res,f1[u]+f2[u]);//答案求一遍经过该点的最长路径
}
int main()
{
    int n;
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i=1;i<n;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    dfs(1,-1);
    cout<<res<<endl;
}

树的中心 

题目 树的中心icon-default.png?t=M85Bhttps://www.acwing.com/problem/content/description/1075/

题目 树的最长路径【树形DP】icon-default.png?t=M85Bhttps://www.acwing.com/problem/content/description/1074/

 

 

 

 

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=2*N,INF=0x3f3f3f3f;
int e[M],ne[M],h[N],idx,w[M];
int d1[N],d2[N],p1[N],p2[N],up[N];

void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int dfs_down(int u,int f)//返回u的最长向下路径
{
    d1[u]=-INF,d2[u]=-INF;
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(j==f)continue; //避免向上查找,进入死循环
        dfs_down(j,u);
        int m=d1[j]+w[i];
        if(m>=d1[u])//更新一下最长和第二长的路径,并记录下从该路径是从哪一个点下去的
        {
            d2[u]=d1[u],p2[u]=p1[u];
            d1[u]=m,p1[u]=j;
        }
        else if(m>d2[u]) d2[u]=m,p2[u]=j;
    }
    if(d1[u]==-INF) d1[u]=d2[u]=0;  //如果没有改变过该点的距离,就证明这个点是叶节点
    return d1[u];
}
void dfs_up(int u,int f)  //用父节点更新一下子节点向上的最长路径
{
    for(int i=h[u];~i;i=ne[i])
    {
        int j=e[i];
        if(j==f)continue;
        if(p1[u]==j) up[j]=max(up[u],d2[u])+w[i]; //如果从父节点向下的最长路径进过了要更新的子节点,那么就用第二长的路径更新
        else up[j]=max(up[u],d1[u])+w[i]; //假如不经过自己,则用向上走的最大值更新自己
        dfs_up(j,u);
    }
}
int main()
{
    int n;
    cin>>n;
    memset(h,-1,sizeof h);
    for(int i=1;i<n;i++ )
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    dfs_down(1,-1);//向下走
    dfs_up(1,-1);//向上走
    int ans=INF;
    for(int i=1;i<=n;i++)
    ans=min(ans,max(up[i],d1[i]));
    cout<<ans<<endl;
}

数字转换【树形DP】

 题目 数字转换【树形DP】icon-default.png?t=M85Bhttp://ybt.ssoier.cn:8088/problem_show.php?pid=1577

#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10;
int e[N],ne[N],h[N],idx,w[N];
int sum[N];
bool st[N];//用来标记该点的有父节点
int ans;
void add(int a,int b)
{
	e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
int dfs(int u)
{
	int d1=0,d2=0;
	for(int i=h[u];~i;i=ne[i])
	{
		int j=e[i];
		int m=dfs(j)+1;
		if(m>=d1)d2=d1,d1=m;
		else if(m>d2)d2=m;
	}
	ans=max(ans,d1+d2);
	return d1;//返回u为根的最大距离
}
int main()
{
	int n;
	cin>>n;
	memset(h,-1,sizeof h);
	for(int i=1;i<=n;i++)//求一个数的约数和
	for(int j=2;j<=n/i;j++)//用类似质数筛的方法
	{
		sum[i*j]+=i;//该数加上这个约数
	}
	for(int i=2;i<=n;i++)//因为1没有在约了,所以从2开始
	{
		if(sum[i]<i)
		{
			add(sum[i],i);//把i的父节点令为sum[i]
			st[i]=true;//标记这个点有父节点
		}
	} 
	for(int i=1;i<=n;i++)
	{
		if(!st[i])
		dfs(i);
	}
	cout<<ans<<endl;
} 

二叉苹果树【有依赖背包DP】

题目 icon-default.png?t=M85Bhttp://ybt.ssoier.cn:8088/problem_show.php?pid=1575

 

 

#include<bits/stdc++.h>
using namespace std;
const int N=110,M=2*N;
int n,m;
int h[N],e[M],idx,ne[M],w[M];
int f[N][M];
void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u,int father)
{
    for(int i=h[u];~i;i=ne[i])//枚举物品组
    {
        if(e[i]==father) continue;
        dfs(e[i],u);
        for(int j=m;j>=0;j--)//体积
            for(int k=0;k<j;k++)//枚举决策
             f[u][j]=max(f[u][j],f[u][j-k-1]+f[e[i]][k]+w[i]);
    }
}
int main()
{
  cin>>n>>m;
  memset(h,-1,sizeof h);
  for(int i=1;i<n;i++)
  {
      int a,b,c;
      cin>>a>>b>>c;
      add(a,b,c),add(b,a,c);
  }
   dfs(1,-1);
   cout<<f[1][m]<<endl;//输出第一个根的m个树枝
   return 0;
}

 战略游戏【树形DP+状态机模型】

题目 战略游戏icon-default.png?t=M85Bhttps://www.acwing.com/problem/content/description/325/

解析 icon-default.png?t=M85Bhttps://www.acwing.com/solution/content/66365/

#include<bits/stdc++.h>
using namespace std;
const int N=1510,M=2*N;
int n;
int h[N],e[M],idx,ne[M];
bool st[N];
int f[N][M];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
    f[u][0]=0;//表示不放哨兵
    f[u][1]=1;//表示放一个哨兵
    for(int i=h[u];~i;i=ne[i])//枚举子树
    {
        int j=e[i];
        dfs(j);
        f[u][0]+=f[j][1];//当前没哨兵加上子树一定得有哨兵
        f[u][1]+=min(f[j][0],f[j][1]);//当前有哨兵,子树可有可无
    }
}
int main()
{
  while(scanf("%d",&n)!=EOF)
  {
      memset(h,-1,sizeof h);//清空上一次的状态
      idx=0;//清空上一次的状态
      memset(st,0,sizeof st);//清空上一次的状态
      for(int i=0;i<n;i++)
      {
          int a,cnt;
          scanf("%d:(%d)",&a,&cnt);
          while(cnt--)
          {
              int b;
              scanf("%d",&b);
              add(a,b);
              st[b]=true;
          }
      }
  int root=0;
  while(st[root]) root++;//找根节点
  dfs(root);
  printf("%d\n",min(f[root][0],f[root][1]));//输出放或者不放的最小值
  }
   return 0;
}

 皇宫看守【树形DP+状态机模型】

题目 皇宫看守icon-default.png?t=M85Bhttp://ybt.ssoier.cn:8088/problem_show.php?pid=1579

解析 icon-default.png?t=M85Bhttps://www.acwing.com/solution/content/66594/

 

 

 

 

#include<bits/stdc++.h>
using namespace std;
const int N=1510,M=2*N;
int n;
int h[N],e[M],idx,ne[M],w[M];
bool st[N];
int f[N][M];
void add(int a,int b)
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u)
{
    f[u][2]=w[u];//表示放一个哨兵,所花费的费用
    for(int i=h[u];~i;i=ne[i])//枚举子树
    {
        int j=e[i];
        dfs(j);
        f[u][0]+=min(f[j][1],f[j][2]);//当前被父节点看到,则子节点只能自己放或者被子节点的子节点看到,两边取最小
        f[u][2]+=min(f[j][0],min(f[j][1],f[j][2]));//当前有哨兵,则子树三种情况都有可能,取最小
    }
    f[u][1]=0x3f3f3f3f;//因为要求最小值,所以初始化为正无穷
    for(int i=h[u];~i;i=ne[i])//枚举子树
    {
        int k=e[i];
        //这里f[u][0]就是以u根的子树所有f[j][1]与f[j][2]的最小值的和,因为前面更新状态时已经求过
        //所以下面就得减去除第k的f[k][1],f[k][2]的最小值的和,剩下就是状态计算的所求
        f[u][1]=min(f[u][1],f[k][2]+f[u][0]-min(f[k][1],f[k][2]));//表示第k个子树有哨兵,可以看到父节点,其他的只能是要么被儿子看到要么自己也放
    }
}
int main()
{
      cin>>n;
      memset(h,-1,sizeof h);//清空上一次的状态
      for(int i=1;i<=n;i++)
      {
          int a,c,cnt;
          cin>>a>>c>>cnt;
          w[a]=c;
          while(cnt--)
          {
              int b;
              cin>>b;
              add(a,b);
              st[b]=true;
          }
      }
  int root=1;
  while(st[root]) root++;//找根节点
  dfs(root);
  printf("%d\n",min(f[root][1],f[root][2]));//输出被子节点看到或者放的最小值
   return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值