树形DP YBTOJ专项

被自己之前的代码丑哭了:(

没有上司的舞会&树上求和

给出一个 n 个结点的树,每个结点都有一个权值 a ,请你选择一些结点,使得被选结点的权值和最大,且被选结点之间不能存在父子关系,即选了父亲不能选儿子,选了儿子不能选父亲。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1000010;
int n,tot,f[N][2],ans,head[N],leader[N];
bool vis[N];
struct edge{int to,next;}e[N<<1];
int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void add_edge(int u,int v)
{
    e[++tot].to=v;
    e[tot].next=head[u];
    head[u]=tot;
}
void dfs(int u)
{
    vis[u]=1;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(!vis[v])
        {
            dfs(v);
            f[u][1]+=f[v][0];
            f[u][0]+=max(f[v][0],f[v][1]);
        }
    }
}
signed main()
{
    n=read();
    for(int i=1;i<=n;i++) f[i][1]=read();
    for(int i=1;i<n;i++)
    {
        int v=read();
        int u=read();
        leader[v]=1;
        add_edge(u,v);
    }
    for(int i=1;i<=n;i++)
    {
        if(leader[i]==0)
        {
            dfs(i);
            printf("%lld",max(f[i][0],f[i][1]));
            return 0;
        }
    }
    return 0;
}

结点覆盖

给出一棵有根树,要求选出一些结点。若选中某个结点,则它本身、它的父亲结点和儿子结点被覆盖。选出第 i 个结点需要一定的花费 ,求覆盖这棵树所需的最小花费。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=100010;
int n,a[N],tot,head[N],f[N][3];
struct edge{int to,next;}e[N<<1];
int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void add_edge(int u,int v)
{
    e[++tot].to=v;
    e[tot].next=head[u];
    head[u]=tot;
}
void dfs(int u,int fa)
{
    f[u][0]=a[u];
    bool flag=0;
    int res=1e18;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(fa==v) continue;
        dfs(v,u);
        f[u][0]+=min(f[v][0],min(f[v][1],f[v][2]));
        f[u][2]+=min(f[v][0],f[v][1]);
        if(f[v][0]<f[v][1]) flag=1;
        else res=min(res,f[v][0]-f[v][1]);
        f[u][1]+=min(f[v][0],f[v][1]);
    }
    if(!flag) f[u][1]+=res;
}
signed main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        int u=read();
        a[i]=read();
        int m=read();
        for(int j=1;j<=m;j++)
        {
            int v=read();
            add_edge(u,v);
            add_edge(v,u);
        }
    }
    dfs(1,0);
    printf("%lld",min(f[1][0],f[1][1]));
    return 0;
}

最长距离

给出一个以 1 为根的 n 个结点的树,树边有权值,求出每个结点与相距最远结点间的距离 。

dfs1先只向下找,dfs2去取向下/先向上再向下的最大值,就是要特判一下u是否本身就在父结点的最长子链中。

#include<bits/stdc++.h>
using namespace std;
#define int long long
struct edge{int to,next,w;}e[10010];
int n,m,ans[10010],head[10010],f[10010][3];
void add_edge(int u,int v,int w)
{
	e[++m].to=v;
	e[m].w=w;
	e[m].next=head[u];
	head[u]=m;
}
void dfs1(int u)
{
	int maxx1,maxx2;
	maxx1=maxx2=0;
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		dfs1(v);
		if(f[v][0]+e[i].w>maxx1)
		{
			ans[u]=v;
			maxx2=maxx1;
			maxx1=f[v][0]+e[i].w;
		}
		else if(f[v][0]+e[i].w>maxx2) maxx2=f[v][0]+e[i].w;
	}
	f[u][0]=maxx1;
	f[u][1]=maxx2;
}
void dfs2(int u)
{
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(ans[u]==v) f[v][2]=max(f[u][1],f[u][2])+e[i].w;
		else f[v][2]=max(f[u][0],f[u][2])+e[i].w;
		dfs2(v);
	}
}
signed main()
{
	while(scanf("%d",&n)!=EOF)
	{
		memset(head,0,sizeof head);
		m=f[1][0]=f[1][1]=f[1][2]=0;
		for(int i=2;i<=n;i++)
		{
			int fa,w;
			scanf("%d%d",&fa,&w);
			add_edge(fa,i,w);
			f[i][0]=f[i][1]=f[i][1]=0;
		}
		dfs1(1);dfs2(1);
		for(int i=1;i<=n;i++) printf("%d\n",max(f[i][0],f[i][2]));
	}
	return 0;
}

选课方案

在大学里,每个学生为了达到一定的学分,必须从很多课程里选择一些课程来学习,有些课程必须在某些课程之前学习,如高等数学总是在其它课程之前学习。现在有 门功课,每门课有个学分,每门课有一门或没有直接先修课(若课程 a 是课程 b 的先修课即只有学完了课程 a ,才能学习课程 b )。一个学生要从这些课程里选择 m 门课程学习,问他能获得的最大学分是多少?

有一个点:森林无疑是难搞的,但是可以假设有一个点是所有根节点的父亲,那就好办了许多。 f[i][j] 表示对于节点 i , 一共选择 j 门课所能得到的最大学分(包括他本身)。选择他的所有子节点的前提是一定要选择这个节点,所 f[i][1] 一定等于 i 节点的权值

在枚举 j 的时候需要注意:由于 f[u][j] 由 f[u][j - k] 更新得到,那么就需要保证更新的时候 f[u][j - k] 没有被更新,由于 k 是一个正整数,所以我们倒序枚举 j 即可

#include<bits/stdc++.h>
using namespace std;
int n,m,f[1010][1010],head[100010],cnt;
struct edge{int u,v; }e[100010];
void add(int from,int to)
{
	e[++cnt].v=head[from];
	e[cnt].u=to;
	head[from]=cnt;
}
void dp(int now)
{
	for(int i=head[now];i;i=e[i].v)
	{
		int go=e[i].u;
		dp(go);
		for(int j=m+1;j>=1;j--)
		for(int k=0;k<j;k++)
		    f[now][j]=max(f[now][j],f[go][k]+f[now][j-k]);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int fa;
		scanf("%d%d",&fa,&f[i][1]);
		add(fa,i);
	}
	dp(0);
	printf("%d",f[0][m+1]);
	return 0;
}

路径求和

给出一棵树,求出所有至少一个端点为叶节点的有向路径的权值和的和。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=5e5+10;
int n,m,root,tot,ans,cnt;
int head[N],du[N],son[N],num[N];
struct edge{int to,next,w;}e[N*2];
int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void add_edge(int u,int v,int w)
{
	e[++tot].to=v;
	e[tot].w=w;
	e[tot].next=head[u];
	head[u]=tot;
}
void dfs(int u,int fa)
{
	num[u]=1;
	if(du[u]==1) cnt++,son[u]=1;
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(fa==v) continue;
		dfs(v,u);
		son[u]+=son[v];
		num[u]+=num[v];
	}
}
void find(int u,int fa)
{
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(fa==v) continue;
		find(v,u);
		ans+=e[i].w*(son[v]*(n-num[v])+num[v]*(cnt-son[v]));
	}
}
signed main()
{
	n=read(),m=read();
	for(int i=1;i<=m;i++)
	{
		int u,v,w;
		w=read(),u=read(),v=read();
		du[u]++;du[v]++;
		add_edge(u,v,w);
		add_edge(v,u,w);
	}
	dfs(1,0);find(1,0);
	printf("%lld",ans);
	return 0;
}

树上移动

在给定的树上:

一个人从 s 出发,求经过所有点的最短长度。
两个人从 s 出发,走的路径没有限制,求经过所有点的最短长度。

#include<bits/stdc++.h>
using namespace std;
struct edge{int to,next,w;}e[500010];
int n,m,sum,tot,f1[100010],f2[100010],head[100010];
int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
void add_edge(int u,int v,int w)
{
	e[++tot].to=v;
	e[tot].w=w;
	e[tot].next=head[u];
	head[u]=tot;
}
void dfs(int u,int fa)
{
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(fa==v) continue;
		dfs(v,u);
		if(f1[v]+e[i].w>f1[u])
		{
			f2[u]=f1[u];
			f1[u]=f1[v]+e[i].w;
		}
		else if(f1[v]+e[i].w>f2[u]) f2[u]=f1[v]+e[i].w;
	}
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<n;i++)
	{
		int u,v,w;
		u=read(),v=read(),w=read();
		add_edge(u,v,w);
		add_edge(v,u,w);
		sum+=w;
	}
	dfs(m,0);
	printf("%d\n",sum*2-f1[m]);
	int maxx=0;
	for(int i=1;i<=n;i++)
	{
		f1[i]+=f2[i];
		maxx=max(maxx,f1[i]);
	}
	printf("%d",sum*2-maxx);
	return 0;
}

块的计数

给定一棵 n 个节点的树,每个点有个喜欢程度,要求选一个联通块,并且这个联通块包含最大的喜欢程度的方案数,输出的方案数对 998244353 取模。

一个点也是联通块!!!正难则反,用联通块数减去不含最大值的联通块数,递归联通块数的时候用乘法原理就好。而且不保证点权为正,初始值赋0的话只有30pts/orz

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=100010;
const int mod=998244353;
int n,tot,mx,head[N],a[N],f[N],g[N],sum,ans;
struct edge{int to,next;}e[N<<1];
int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void add_edge(int u,int v)
{
    e[++tot].to=v;
    e[tot].next=head[u];
    head[u]=tot;
}
void dfs(int u,int fa)
{
    g[u]=1;
    if(a[u]==mx) f[u]=0;
    else f[u]=1;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(fa==v) continue;
        dfs(v,u);
        f[u]=f[u]*(f[v]+1)%mod;
        g[u]=g[u]*(g[v]+1)%mod;
    }
    sum=(sum+g[u])%mod;
    ans=(ans+f[u])%mod;
}
signed main()
{
    n=read();mx=-1e18;
    for(int i=1;i<=n;i++)
    {
        a[i]=read();
        mx=max(mx,a[i]);
    }
    for(int i=1;i<n;i++)
    {
        int u=read();
        int v=read();
        add_edge(u,v);
        add_edge(v,u);
    }
    dfs(1,0);
    printf("%lld",(sum+mod-ans)%mod);
    return 0;
}

权值统计

给出一个 n 个结点的无根树以及每个结点的权值,求出树的每一条路径的权值积的和,单独的一个结点也算作一条路径。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=10086;
int n,ans,a[100010],f[100010],head[100010],tot;
struct edge{int to,next;}e[500010];
void add_edge(int u,int v)
{
	e[++tot].to=v;
	e[tot].next=head[u];
	head[u]=tot;
}
void dfs(int u,int fa)
{
	int sum,tot;
	sum=tot=0;
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to;
		if(fa==v) continue;
		dfs(v,u);
		sum+=f[v];
		tot+=f[v]*f[v];
	}
	f[u]=(sum+1)*a[u]%mod;
	ans+=f[u]+(sum*sum-tot)*a[u]/2%mod;
	ans%=mod;
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i< n;i++)
	{
		int u,v;
		scanf("%lld%lld",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	dfs(1,0);
	printf("%lld",ans);
	return 0;
}

树的合并

给定两棵树,则总共有 n*m 种方案把这两棵树通过加一条边连成一棵树,那这 n*m 棵树的直径大小之和是多少呢?树的直径指的是树上的最长简单路径。

树的合并(ybtoj-树上dp)_wind__whisper的博客-CSDN博客_树的合并

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
树形动态规划(Tree DP)是一种常用的动态规划算法,用于解决树结构相关的问题。在Python中,可以使用递归或者迭代的方式实现树形DP树形DP的基本思想是,从树的叶子节点开始,逐层向上计算每个节点的状态,并利用已经计算过的节点状态来更新当前节点的状态。这样可以通过自底向上的方式,逐步计算出整个树的最优解。 下面是一个简单的示例,演示如何使用树形DP解决一个二叉树中节点权值之和的最大值问题: ```python class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def max_sum(root): if root is None: return 0 # 递归计算左右子树的最大权值和 left_sum = max_sum(root.left) right_sum = max_sum(root.right) # 当前节点的最大权值和为当前节点值加上左右子树中较大的权值和 return root.val + max(left_sum, right_sum) # 构建一个二叉树 root = TreeNode(1) root.left = TreeNode(2) root.right = TreeNode(3) root.left.left = TreeNode(4) root.left.right = TreeNode(5) # 计算二叉树中节点权值之和的最大值 result = max_sum(root) print(result) ``` 这段代码中,我们定义了一个`TreeNode`类来表示二叉树的节点,其中`val`表示节点权值,`left`和`right`分别表示左子节点和右子节点。`max_sum`函数使用递归的方式计算二叉树中节点权值之和的最大值,通过比较左右子树的最大权值和来确定当前节点的最大权值和。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiyuping24

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值