Part 4.4 树形动态规划

树形动态规划,即在树上进行的动态规划。

因为树的递归性质,树形动态规划一般都是递归求解的。

没有上司的舞会

题目描述

某大学有 n n n 个职员,编号为 1 … n 1\ldots n 1n

他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。

现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 r i r_i ri,但是呢,如果某个职员的直接上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。

所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。

输入格式

输入的第一行是一个整数 n n n

2 2 2 到第 ( n + 1 ) (n + 1) (n+1) 行,每行一个整数,第 ( i + 1 ) (i+1) (i+1) 行的整数表示 i i i 号职员的快乐指数 r i r_i ri

( n + 2 ) (n + 2) (n+2) 到第 2 n 2n 2n 行,每行输入一对整数 l , k l, k l,k,代表 k k k l l l 的直接上司。

输出格式

输出一行一个整数代表最大的快乐指数。

样例 #1

样例输入 #1

7
1
1
1
1
1
1
1
1 3
2 3
6 4
7 4
4 5
3 5

样例输出 #1

5

提示

数据规模与约定

对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 6 × 1 0 3 1\leq n \leq 6 \times 10^3 1n6×103 − 128 ≤ r i ≤ 127 -128 \leq r_i\leq 127 128ri127 1 ≤ l , k ≤ n 1 \leq l, k \leq n 1l,kn,且给出的关系一定是一棵树。

解题思路

dp[i][0/1]:以第i个节点为根节点的子树的最大快乐值,0表示不选这个节点,1表示选这个节点

dp[i][0]=sum(max(dp[k][0],dp[k][1]))  (k为i的子节点的编号)
dp[i][1]=sum(dp[k][0])

代码实现

#include<iostream>
#include<vector>
using namespace std;
#define MAX_N 6000
int dp[MAX_N+5][5];
int val[MAX_N+5];
vector<int>son[MAX_N+5];
vector<int>fa[MAX_N+5];
void dfs(int now)
{
	dp[now][0]=0;
	dp[now][1]=val[now];
	for(auto i:son[now])
	{
		dfs(i);
		dp[now][1]+=dp[i][0];
		dp[now][0]+=max(dp[i][0],dp[i][1]);
	}
	return ;
}
int main()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)cin>>val[i];
	for(int i=1,a,b;i<=n-1;i++)
	{
		cin>>a>>b;
		fa[a].push_back(b);
		son[b].push_back(a);
	}
	int root;
	for(int i=1;i<=n;i++)
	if(!fa[i].size())
	{
		root=i;
		break;
	}
	dfs(root);
	cout<<max(dp[root][1],dp[root][0]);
	return 0;
 } 

[NOIP2003 提高组] 加分二叉树

题目描述

设一个 n n n 个节点的二叉树 tree \text{tree} tree 的中序遍历为 ( 1 , 2 , 3 , … , n ) (1,2,3,\ldots,n) (1,2,3,,n),其中数字 1 , 2 , 3 , … , n 1,2,3,\ldots,n 1,2,3,,n 为节点编号。每个节点都有一个分数(均为正整数),记第 i i i 个节点的分数为 d i d_i di tree \text{tree} tree 及它的每个子树都有一个加分,任一棵子树 subtree \text{subtree} subtree(也包含 tree \text{tree} tree 本身)的加分计算方法如下:

subtree \text{subtree} subtree 的左子树的加分 × \times × subtree \text{subtree} subtree 的右子树的加分 + + + subtree \text{subtree} subtree 的根的分数。

若某个子树为空,规定其加分为 1 1 1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。

试求一棵符合中序遍历为 ( 1 , 2 , 3 , … , n ) (1,2,3,\ldots,n) (1,2,3,,n) 且加分最高的二叉树 tree \text{tree} tree。要求输出

  1. tree \text{tree} tree 的最高加分。

  2. tree \text{tree} tree 的前序遍历。

输入格式

1 1 1 1 1 1 个整数 n n n,为节点个数。

2 2 2 n n n 个用空格隔开的整数,为每个节点的分数

输出格式

1 1 1 1 1 1 个整数,为最高加分($ Ans \le 4,000,000,000$)。

2 2 2 n n n 个用空格隔开的整数,为该树的前序遍历。

样例 #1

样例输入 #1

5
5 7 1 2 10

样例输出 #1

145
3 1 2 4 5

提示

数据规模与约定

对于全部的测试点,保证 1 ≤ n < 30 1 \leq n< 30 1n<30,节点的分数是小于 100 100 100 的正整数,答案不超过 4 × 1 0 9 4 \times 10^9 4×109

解题思路

本题也是树上问题,但是和前两道题不同的是本题按照中序遍历顺序给出了每个点的值,而前两题按照的是父子关系。中序遍历的顺序也就是dfs的顺序,也正因此本题不需要像前面个两道题一样进行dfs+记忆化,本题可以直接按照区间dp的思路来实现。
dp[i][j]表示区间i到j的最大分数
dp[i][j]=max(dp[i][j],dp[i][k-1]*dp[k+1][j]+dp[k][k])
如何输出前序的顺序?记录每一段区间的根节点,之后按照先序遍历输出每一段的根节点。

代码实现

#include<iostream>
using namespace std;
#define MAX_N 30
int dp[MAX_N+5][MAX_N+5];
int root[MAX_N+5][MAX_N+5];
int n;
void output(int l,int r)
{
	if(l>r)return ;
	int u=root[l][r];
	cout<<u<<" ";
	output(l,u-1);
	output(u+1,r);
	return ;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)cin>>dp[i][i],root[i][i]=i;
	for(int i=0;i<=n;i++)dp[i+1][i]=1;
	for(int l=2;l<=n;l++)
	{
		for(int i=1;i<=n-l+1;i++)
		{
			int j=i+l-1;
			for(int k=i;k<=j;k++)
			{
				if(dp[i][j]<dp[i][k-1]*dp[k+1][j]+dp[k][k])
				{
					dp[i][j]=dp[i][k-1]*dp[k+1][j]+dp[k][k];
					root[i][j]=k;
				}
			}
		}
	}
	cout<<dp[1][n]<<endl;
	output(1,n);
	return 0;
 } 

最大子树和

题目描述

小明对数学饱有兴趣,并且是个勤奋好学的学生,总是在课后留在教室向老师请教一些问题。一天他早晨骑车去上课,路上见到一个老伯正在修剪花花草草,顿时想到了一个有关修剪花卉的问题。于是当日课后,小明就向老师提出了这个问题:

一株奇怪的花卉,上面共连有 N N N 朵花,共有 N − 1 N-1 N1 条枝干将花儿连在一起,并且未修剪时每朵花都不是孤立的。每朵花都有一个“美丽指数”,该数越大说明这朵花越漂亮,也有“美丽指数”为负数的,说明这朵花看着都让人恶心。所谓“修剪”,意为:去掉其中的一条枝条,这样一株花就成了两株,扔掉其中一株。经过一系列“修剪“之后,还剩下最后一株花(也可能是一朵)。老师的任务就是:通过一系列“修剪”(也可以什么“修剪”都不进行),使剩下的那株(那朵)花卉上所有花朵的“美丽指数”之和最大。

老师想了一会儿,给出了正解。小明见问题被轻易攻破,相当不爽,于是又拿来问你。

输入格式

第一行一个整数 n   ( 1 ≤ N ≤ 16000 ) n\ (1\le N\le 16000) n (1N16000)。表示原始的那株花卉上共 n n n 朵花。

第二行有 n n n 个整数,第 i i i 个整数表示第 i i i 朵花的美丽指数。

接下来 n − 1 n-1 n1 行每行两个整数 a , b a,b a,b,表示存在一条连接第 a a a 朵花和第 b b b 朵花的枝条。

输出格式

一个数,表示一系列“修剪”之后所能得到的“美丽指数”之和的最大值。保证绝对值不超过 2147483647 2147483647 2147483647

样例 #1

样例输入 #1

7
-1 -1 -1 1 1 1 0
1 4
2 5
3 6
4 7
5 7
6 7

样例输出 #1

3

提示

数据范围及约定

  • 对于 60 % 60\% 60% 的数据,有 1 ≤ N ≤ 1000 1\le N\le 1000 1N1000
  • 对于 100 % 100\% 100% 的数据,有 1 ≤ N ≤ 16000 1\le N\le 16000 1N16000

解题思路

dp[i]:以i为根的子树的最大魅力值。
dp[i]=a[i]+sum(max(0,dp[j]))
以i为根的子树的最大魅力值为节点i的魅力值与子树剪枝或不剪枝的最大魅力值得和。

代码实现

#include<iostream>
#include<vector>
using namespace std;
#define MAX_N 16000
int dp[MAX_N+5];
vector<int>son[MAX_N+5];
int n,ans=-2147483647;
void dfs(int now,int before)
{
	for(auto i:son[now])
	{
		if(i==before)continue;
		dfs(i,now);
		dp[now]+=max(0,dp[i]);
	}
	return ;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)cin>>dp[i];
	for(int i=1,x,y;i<=n-1;i++)
	{
		cin>>x>>y;
		son[x].push_back(y);
		son[y].push_back(x);
	}
	dfs(1,0);
	for(int i=1;i<=n;i++)ans=max(ans,dp[i]);
	cout<<ans;
	return 0;
}

[CTSC1997] 选课

题目描述

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

输入格式

第一行有两个整数 N N N , M M M 用空格隔开。( 1 ≤ N ≤ 300 1 \leq N \leq 300 1N300 , 1 ≤ M ≤ 300 1 \leq M \leq 300 1M300 )

接下来的 N N N 行,第 I + 1 I+1 I+1 行包含两个整数 $k_i $和 s i s_i si, k i k_i ki 表示第I门课的直接先修课, s i s_i si 表示第I门课的学分。若 k i = 0 k_i=0 ki=0 表示没有直接先修课( 1 ≤ k i ≤ N 1 \leq {k_i} \leq N 1kiN , 1 ≤ s i ≤ 20 1 \leq {s_i} \leq 20 1si20)。

输出格式

只有一行,选 M M M 门课程的最大得分。

样例 #1

样例输入 #1

7  4
2  2
0  1
0  4
2  1
7  1
7  6
2  2

样例输出 #1

13

解题思路

本题是树上背包问题。
dp[i][j][k]表示以第i个点为根节点,选择j个包含自己与子节点在内的j个点在选择数量为k的情况下的最大分数。

dp[i][j][k]=max(dp[i][j-1][k],dp[i][j-1][k-l]+dp[c][c_son_cnt][l])

l表示分给第j个节点的可选数量,c表示第j个孩子节点的编号,c_son_cnt表示孩子节点c的孩子数量。

代码实现

(直接按照状态转移方程翻译的版本)

#include<iostream>
#include<vector>
using namespace std;
#define MAX_N 300
int dp[MAX_N+5][MAX_N+5];
vector<int>fa[MAX_N+5];
vector<int>son[MAX_N+5];
int k[MAX_N+5];
int n,m;
void dfs(int now)
{
	dp[now][1]=k[now];
	for(auto i:son[now])
	{
		dfs(i);
		for(int j=m+1;j>=1;j--)
		{
			for(int l=0;l<j;l++)
			{
				dp[now][j]=max(dp[now][j],dp[i][l]+dp[now][j-l]);
			}
		}
	}
	return ;
}
int main()
{
	cin>>n>>m;
	for(int i=1,a,b;i<=n;i++)
	{
		cin>>a>>b;
		fa[i].push_back(a);
		son[a].push_back(i);
		k[i]=b;
	}
	int root=0;
	dfs(root);
	cout<<dp[0][m+1];
	return 0;
}

(空间优化版本)

#include<iostream>
#include<vector>
using namespace std;
#define MAX_N 300
int dp[MAX_N+5][MAX_N+5];
vector<int>fa[MAX_N+5];
vector<int>son[MAX_N+5];
int k[MAX_N+5];
int n,m;
void dfs(int now)
{
	dp[now][1]=k[now];
	for(auto i:son[now])
	{
		dfs(i);
		for(int j=m+1;j>=1;j--)
		{
			for(int l=0;l<j;l++)
			{
				dp[now][j]=max(dp[now][j],dp[i][l]+dp[now][j-l]);
			}
		}
	}
	return ;
}
int main()
{
	cin>>n>>m;
	for(int i=1,a,b;i<=n;i++)
	{
		cin>>a>>b;
		fa[i].push_back(a);
		son[a].push_back(i);
		k[i]=b;
	}
	int root=0;
	dfs(root);
	cout<<dp[0][m+1];
	return 0;
}

有线电视网

题目描述

某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。

从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。

现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。

写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。

输入格式

输入文件的第一行包含两个用空格隔开的整数 N N N M M M,其中 2 ≤ N ≤ 3000 2 \le N \le 3000 2N3000 1 ≤ M ≤ N − 1 1 \le M \le N-1 1MN1 N N N 为整个有线电视网的结点总数, M M M 为用户终端的数量。

第一个转播站即树的根结点编号为 1 1 1,其他的转播站编号为 2 2 2 N − M N-M NM,用户终端编号为 N − M + 1 N-M+1 NM+1 N N N

接下来的 N − M N-M NM 行每行表示—个转播站的数据,第 i + 1 i+1 i+1 行表示第 i i i 个转播站的数据,其格式如下:

K    A 1    C 1    A 2    C 2    …    A k    C k K \ \ A_1 \ \ C_1 \ \ A_2 \ \ C_2 \ \ \ldots \ \ A_k \ \ C_k K  A1  C1  A2  C2    Ak  Ck

K K K 表示该转播站下接 K K K 个结点(转播站或用户),每个结点对应一对整数 A A A C C C A A A 表示结点编号, C C C 表示从当前转播站传输信号到结点 A A A 的费用。最后一行依次表示所有用户为观看比赛而准备支付的钱数。单次传输成本和用户愿意交的费用均不超过 10。

输出格式

输出文件仅一行,包含一个整数,表示上述问题所要求的最大用户数。

样例 #1

样例输入 #1

5 3
2 2 2 5 3
2 3 2 4 3
3 4 2

样例输出 #1

2

提示

样例解释

如图所示,共有五个结点。结点 ① 为根结点,即现场直播站,② 为一个中转站,③④⑤ 为用户端,共 M M M 个,编号从 N − M + 1 N-M+1 NM+1 N N N,他们为观看比赛分别准备的钱数为 3 3 3 4 4 4 2 2 2

从结点 ① 可以传送信号到结点 ②,费用为 2 2 2

也可以传送信号到结点 ⑤,费用为 3 3 3(第二行数据所示);

从结点 ② 可以传输信号到结点 ③,费用为 2 2 2

也可传输信号到结点 ④,费用为 3 3 3(第三行数据所示)。

如果要让所有用户(③④⑤)都能看上比赛,则信号传输的总费用为: 2 + 3 + 2 + 3 = 10 2+3+2+3=10 2+3+2+3=10,大于用户愿意支付的总费用 3 + 4 + 2 = 9 3+4+2=9 3+4+2=9,有线电视网就亏本了,而只让 ③④ 两个用户看比赛就不亏本了。

解题思路

本题依然是背包问题。
dp[i][j][k]:表示以i为根节点得树选择前j个子孩子在服务用户为k得情况下的最大收益。
dp[i][j][k]=max(dp[i][j-1][k-l]+dp[c][c_cnt_son][l])

代码实现

#include<iostream>
#include<vector>
#include<cstring> 
using namespace std;
#define MAX_N 3000
int n,m;
vector<int>son[MAX_N+5];
int dp[MAX_N+5][MAX_N+5];
int val[MAX_N+5][MAX_N+5];
int cnt_p[MAX_N+5];
void dfs(int now)
{
	for(int i=1;i<=son[now].size();i++)
	{	
		int c=son[now][i-1];
		dfs(c);
		cnt_p[now]+=cnt_p[c];
		for(int k=cnt_p[now];k>=1;k--)
		{
			for(int l=1;l<=cnt_p[c]&&l<=k;l++)
			{
				dp[now][k]=max(dp[now][k],dp[now][k-l]+dp[c][l]-val[now][c]);
			}
		}
	}
}
int main()
{
	memset(dp,-0x3f,sizeof dp);
	cin>>n>>m;
	for(int i=1,k;i<=n-m;i++)
	{
		cin>>k;
		for(int j=1,a,b;j<=k;j++)
		{
			cin>>a>>b;
			son[i].push_back(a);
			val[i][a]=b;
		}
	}
	for(int i=1;i<=n;i++)dp[i][0]=0;	
	for(int i=n-m+1;i<=n;i++)
	{
		cin>>dp[i][1];
		cnt_p[i]=1;
	}
	dfs(1);
	for(int i=m;i>=0;i--)
	{
		if(dp[1][i]>=0)
		{
			cout<<i;
			break;
		}
	}
	return 0;
 } 

[ZJOI2006] 三色二叉树

题目描述

一棵二叉树可以按照如下规则表示成一个由 0 0 0 1 1 1 2 2 2 组成的字符序列,我们称之为“二叉树序列 S S S”:

S = { 0 表示该树没有子节点 1 S 1 表示该树有一个节点, S 1 为其子树的二叉树序列 2 S 1 S 2 表示该树由两个子节点, S 1 和 S 2 分别表示其两个子树的二叉树序列 S= \begin{cases} 0& \text表示该树没有子节点\\ 1S_1& 表示该树有一个节点,S_1 为其子树的二叉树序列\\ 2S_1S_2& 表示该树由两个子节点,S_1 和 S_2 分别表示其两个子树的二叉树序列 \end{cases} S= 01S12S1S2示该树没有子节点表示该树有一个节点,S1为其子树的二叉树序列表示该树由两个子节点,S1S2分别表示其两个子树的二叉树序列

例如,下图所表示的二叉树可以用二叉树序列 S = 21200110 S=\texttt{21200110} S=21200110 来表示。

haha.png

你的任务是要对一棵二叉树的节点进行染色。每个节点可以被染成红色、绿色或蓝色。并且,一个节点与其子节点的颜色必须不同,如果该节点有两个子节点,那么这两个子节点的颜色也必须不同。给定一颗二叉树的二叉树序列,请求出这棵树中最多和最少有多少个点能够被染成绿色。

输入格式

输入只有一行一个字符串 s s s,表示二叉树序列。

输出格式

输出只有一行,包含两个数,依次表示最多和最少有多少个点能够被染成绿色。

样例 #1

样例输入 #1

1122002010

样例输出 #1

5 2

提示

数据规模与约定

对于全部的测试点,保证 1 ≤ ∣ s ∣ ≤ 5 × 1 0 5 1 \leq |s| \leq 5 \times 10^5 1s5×105 s s s 中只含字符 0 1 2

解题思路

dp1[i][0/1]:表示以i为根节点的树所能取得的最大绿色节点数量。0表示节点i不为绿色,1表示节点i为绿色。
dp1[i][0]=max(dp1[k1][0]+dp1[k2][1],dp1[k1][1]+dp1[k2][0]);
dp1[i][1]=dp1[k1][0]+dp2[k2][0]; (节点i有2个子节点)
dp1[i][0]=max(dp1[k1][0],dp1[k1][1]);
dp1[i][1]=dp1[k1][0]; (节点i有1个子节点)

为什么dp数组数组第二维只需要设置0/1,而不需要设置0/1/2模拟题目中的三种颜色?因为红色和蓝色是等价的,具有对称性,也就是说一个点为蓝色或者红色其dp值是相等的,所以可以直接用0表示其为蓝色或者红色。

#include<iostream>
#include<string>
#include<vector>
using namespace std;
#define MAX_N 500000
int dp1[MAX_N+5][2];
int dp2[MAX_N+5][2];
vector<int>son[MAX_N+5];
string s;
int cnt=1;
void build()
{
	int now=cnt;
	for(int i=1;i<=s[now-1]-'0';i++)
	{
		son[now].push_back(++cnt);
		build();
	}
	return ;
}
void dfs(int now)
{
	int cnt_son=son[now].size();
	if(cnt_son==2)
	{
		dfs(son[now][0]);
		dfs(son[now][1]);
		dp1[now][0]=max(dp1[son[now][0]][1]+dp1[son[now][1]][0],dp1[son[now][1]][1]+dp1[son[now][0]][0]);
		dp1[now][1]=dp1[son[now][0]][0]+dp1[son[now][1]][0]+1;
		dp2[now][0]=min(dp2[son[now][0]][1]+dp2[son[now][1]][0],dp2[son[now][1]][1]+dp2[son[now][0]][0]);
		dp2[now][1]=dp2[son[now][0]][0]+dp2[son[now][1]][0]+1;
	}
	else if(cnt_son==1)
	{
		dfs(son[now][0]);
		dp1[now][0]=max(dp1[son[now][0]][0],dp1[son[now][0]][1]);
		dp1[now][1]=dp1[son[now][0]][0]+1;
		dp2[now][0]=min(dp2[son[now][0]][0],dp2[son[now][0]][1]);
		dp2[now][1]=dp2[son[now][0]][0]+1;
	}
	else{
		dp1[now][0]=dp2[now][0]=0;
		dp1[now][1]=dp2[now][1]=1;
	}
	return ;
}
int main()
{
	cin>>s;
	build();
	dfs(1);
	cout<<max(dp1[1][0],dp1[1][1])<<" ";
	cout<<min(dp2[1][0],dp2[1][1]);
	return 0;
}

[USACO12FEB] Nearby Cows G

题目描述

Farmer John has noticed that his cows often move between nearby fields. Taking this into account, he wants to plant enough grass in each of his fields not only for the cows situated initially in that field, but also for cows visiting from nearby fields.

Specifically, FJ’s farm consists of N fields (1 <= N <= 100,000), where some pairs of fields are connected with bi-directional trails (N-1 of them in total). FJ has designed the farm so that between any two fields i and j, there is a unique path made up of trails connecting between i and j. Field i is home to C(i) cows, although cows sometimes move to a different field by crossing up to K trails (1 <= K <= 20).

FJ wants to plant enough grass in each field i to feed the maximum number of cows, M(i), that could possibly end up in that field – that is, the number of cows that can potentially reach field i by following at most K trails. Given the structure of FJ’s farm and the value of C(i) for each field i, please help FJ compute M(i) for every field i.

给你一棵 n n n 个点的树,点带权,对于每个节点求出距离它不超过 k k k 的所有节点权值和 m i m_i mi

输入格式

* Line 1: Two space-separated integers, N and K.

* Lines 2…N: Each line contains two space-separated integers, i and j (1 <= i,j <= N) indicating that fields i and j are directly connected by a trail.

* Lines N+1…2N: Line N+i contains the integer C(i). (0 <= C(i) <= 1000)

第一行两个正整数 n , k n,k n,k
接下来 n − 1 n-1 n1 行,每行两个正整数 u , v u,v u,v,表示 u , v u,v u,v 之间有一条边。
最后 n n n 行,每行一个非负整数 c i c_i ci,表示点权。

输出格式

* Lines 1…N: Line i should contain the value of M(i).

输出 n n n 行,第 i i i 行一个整数表示 m i m_i mi

样例 #1

样例输入 #1

6 2 
5 1 
3 6 
2 4 
2 1 
3 2 
1 
2 
3 
4 
5 
6

样例输出 #1

15 
21 
16 
10 
8 
11

提示

There are 6 fields, with trails connecting (5,1), (3,6), (2,4), (2,1), and (3,2). Field i has C(i) = i cows.

Field 1 has M(1) = 15 cows within a distance of 2 trails, etc.

【数据范围】
对于 100 % 100\% 100% 的数据: 1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1n105 1 ≤ k ≤ 20 1 \le k \le 20 1k20 0 ≤ c i ≤ 1000 0 \le c_i \le 1000 0ci1000

解题思路

树形dp求以某个点作为根节点的最大值较为容易,但是本题要求距离某个点k距离内的权重总和,也就是需要考虑父节点之前的情况,本题的关键在于两次的dfs,第一次按照一般思路求出以某点为根节点在k范围内子节点的权重和,第二次再考虑父节点及之前节点的值。
dp1[i][j]:以i为根节点在j范围内子节点的权重和
dp1[i][j]=sum(dp1[c][j-1])+dp[i][0]  (c为孩子节点的编号)
dp2[i][j]:与节点i距离范围为j的节点的权重和
dp2[i][j]=dp1[i][j]+dp2[f][j-1]-dp1[i][j-2] (f为父节点的编号)

代码实现

#include<iostream>
#include<vector>
using namespace std;
#define MAX_N 100000
#define MAX_K 20
int dp1[MAX_N+5][MAX_K+5];
int dp2[MAX_N+5][MAX_K+5];
vector<int>son[MAX_N+5];
int n,k;
void dfs1(int now,int fa)
{
	for(int i=1;i<=k;i++)dp1[now][i]=dp1[now][0];
	for(auto i:son[now])
	{
		if(i==fa)continue;
		dfs1(i,now);//递归放在前,先求子节点的dp1再求父节点 
		for(int c=1;c<=k;c++)
		{
			dp1[now][c]+=dp1[i][c-1];
		}	
	}
	return ;
}
void dfs2(int now,int fa)
{
	for(int i=0;i<=k;i++)dp2[now][i]+=dp1[now][i];
	for(auto i:son[now])
	{
		if(i==fa)continue;
		dp2[i][1]+=dp1[now][0];
		for(int c=2;c<=k;c++)
		{
			dp2[i][c]+=(dp2[now][c-1]-dp1[i][c-2]);
		}
		dfs2(i,now);//递归放在后,父节点的dp2已知,根据父节点求子节点 
	}
	return ;
}
int main()
{
	cin>>n>>k;
	for(int i=1,a,b;i<=n-1;i++)
	{
		cin>>a>>b;
		son[a].push_back(b);
		son[b].push_back(a);
	}
	for(int i=1;i<=n;i++)cin>>dp1[i][0];
	dfs1(1,0);
	dfs2(1,0);
	for(int i=1;i<=n;i++)cout<<dp2[i][k]<<endl;
	return 0;
}

[CQOI2017] 小Q的棋盘

题目描述

小 Q 正在设计一种棋类游戏。

在小 Q 设计的游戏中,棋子可以放在棋盘上的格点中。某些格点之间有连线,棋子只能在有连线的格点之间移动。整个棋盘上共有 V V V 个格点,编号为 0 , 1 , 2 , ⋯   , V − 1 0,1,2,\cdots, V- 1 0,1,2,,V1,它们是连通的,也就是说棋子从任意格点出发,总能到达所有的格点。小 Q 在设计棋盘时,还保证棋子从一个格点移动到另外任一格点的路径是唯一的。

小 Q 现在想知道,当棋子从格点 0 0 0 出发,移动 N N N 步最多能经过多少格点。格点可以重复经过多次,但不重复计数。

输入格式

第一行包含2个正整数 V , N V, N V,N,其中 V V V 表示格点总数, N N N 表示移动步数。

接下来 V − 1 V-1 V1 行,每行两个数 a i , b i a_i,b_i ai,bi,表示编号为 a i , b i a_i,b_i ai,bi 的两个格点之间有连线。

输出格式

输出一行一个整数,表示最多经过的格点数量。

样例 #1

样例输入 #1

5 2
1 0
2 1
3 2
4 3

样例输出 #1

3

样例 #2

样例输入 #2

9 5
0 1
0 2
2 6
4 2
8 1
1 3
3 7
3 5

样例输出 #2

5

提示

【输入输出样例 1 说明】

从格点 0 0 0 出发移动 2 2 2 步。经过 0 , 1 , 2 0, 1, 2 0,1,2 3 3 3 个格点。

【输入输出样例 2 说明】

一种可行的移动路径为 0 → 1 → 3 → 5 → 3 → 7 0 \to 1 \to 3 \to 5 \to 3 \to 7 013537,经过 0 , 1 , 3 , 5 , 7 0, 1, 3, 5, 7 0,1,3,5,7 5 5 5 个格点。

【数据规模与约定】

对于 100 % 100\% 100% 的测试点, 1 ≤ N , V ≤ 100 1\le N,V ≤ 100 1N,V100 0 ≤ a i , b i < V 0 ≤a_i,b_i< V 0ai,bi<V

解题思路

dp[0/1][i][j][k]:以i为根节点对于包含节点i与子节点在内的j个节点走k步所经过的最多点的数量。其中0表示不用回到节点i,1表示要回到节点i。
dp[0][i][j][k]=max
(dp[0][i][j-1][k],dp[0][i][j-1][k-l]+dp[1][c][c_cnt_son][l-2],dp[1][i][j-1][k-l]+dp[0][c][c_cnt_son][l-1])

dp[1][i][j][k]=max(dp[1][i][j-1][k-l]+dp[1][c][c_cnt_son][l])

dp[1][c][c_cnt_son][l-2]处为什么是l-2?因为c是i的一个子节点,要想走一个来回还得多加2。
dp[0][c][c_cnt_son][l-1]处为什么是l-1?不需要走来回,只需要从节点i到达节点c,所以只加1。
注:上面的式子为了便于理解没有省去j这一维度,程序实现时会做空间优化。

代码实现

#include<iostream>
#include<vector>
using namespace std;
#define MAX_N 100
int dp[2][MAX_N+5][MAX_N+5];
vector<int>son[MAX_N+5];
int v,n;
void dfs(int now,int fa)
{
	for(int i=1;i<=son[now].size();i++)
	{
		int c=son[now][i-1];
		if(c==fa)continue;
		dfs(c,now);
		dp[0][now][1]=2;
		for(int k=n;k>=2;k--)
		{
			for(int l=1;l<=k;l++)
			{
				int temp;
				if(l==1)temp=dp[0][c][l-1]+dp[1][now][k-l];
				else temp=max(dp[0][now][k-l]+dp[1][c][l-2],dp[0][c][l-1]+dp[1][now][k-l]);
				dp[0][now][k]=max(dp[0][now][k],temp);
				if(k%2==1)continue;
				if(l>1)dp[1][now][k]=max(dp[1][now][k],dp[1][now][k-l]+dp[1][c][l-2]);
			}
		}
	}
	return ;
}
int main()
{
	cin>>v>>n;
	for(int i=1,a,b;i<=v-1;i++)
	{
		cin>>a>>b;
		son[a].push_back(b);
		son[b].push_back(a);
	}
	for(int i=0;i<v;i++)dp[0][i][0]=dp[1][i][0]=1;
	dfs(0,0);
	cout<<dp[0][0][n];
	return 0;
 } 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/719982.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

swagger手动添加cookies信息、浏览器中手动添加cookies信息

在实际项目使用中&#xff0c;我们可能需要把登录token放到cookies中&#xff0c;请求时从cookies中获取token值&#xff0c;以此校验用户登录信息&#xff0c;以下整理一下怎么在cookies中手动添加数据。 操作步骤如下&#xff1a; (1)&#xff1a;F12打开调试工具&#xff…

CP AUTOSAR标准之LargeDataCOM(AUTOSAR_CP_SWS_LargeDataCOM)

1 简介和功能概述 该规范描述了AUTOSAR基础软件模块LdCom的功能、API和配置。   在AUTOSAR分层架构中,AUTOSAR LdCom模块位于RTE/SwCluC_LdComProxy和PDU路由之间,参见[1,EXP LayeredSoftwareArchitecture]。   AUTOSAR LdCom模块提供了一种替代的交互层机制。通过专注于…

Python函数与模块知识点

函数 变量作用域 匿名函数 内置函数 递归函数 思维导图 global关键字 在 Python 中&#xff0c;如果你在函数内部想要修改一个全局变量的值&#xff0c;你需要使用 global 关键字来声明这个变量。 python x 10 # 全局变量def change_x(): global x # 声明 x 是全局变量 x 20 …

算法课程笔记——单调栈单调队列

算法课程笔记——单调栈&单调队列

解锁应用新技能:Xinstall助力快速获取H5信息,轻松提升用户体验

在移动互联网时代&#xff0c;数据是驱动应用发展的关键。对于许多开发者来说&#xff0c;如何高效、准确地获取H5信息成为了一个不小的挑战。今天&#xff0c;我们为大家介绍一款强大的工具——Xinstall&#xff0c;它将帮助你轻松解决这一难题&#xff0c;让你的应用数据获取…

Jacob调用Windows桌面软件(Adobe系列,CAD,ABBYY FineReader 等)

参考资料 jacob调用adobe动态库https://blog.csdn.net/love_5209/article/details/19162185 python查看查看 COM 组件的 ProgID 或 CLSID 对应的字符串参数https://blog.51cto.com/u_16175517/10484354加载动态库示例https://baijiahao.baidu.com/s?id=1777971964908285215&…

在Spring Boot中使用Sa-Token实现路径拦截和特定接口放行

在Spring Boot中使用Sa-Token实现路径拦截和特定接口放行 很喜欢的一段话&#xff1a;别想太多&#xff0c;好好生活&#xff0c;也许日子过着过着就会有答案&#xff0c;努力走着走着就会有温柔的着落。 春在路上&#xff0c;花在枝上&#xff0c;所有的美好都在路上&#xff…

【STM32】使用标准库创建一个工程

创建工程 本系列文章中使用的硬件和软件版本如下&#xff1a; CPU型号&#xff1a;STM32F407ZGT6 Keil版本&#xff1a;V5.38.0.0 ARM编译器版本&#xff1a;V5.06 标准库版本&#xff1a;V1.8.0 1.下载标准库 官网链接&#xff1a;STSW-STM32065 - STM32F4 DSP和标准外…

AI金融投资:批量下载巨潮资讯基金招募说明书

打开巨潮资讯的基金招募说明书页面&#xff1a; http://www.cninfo.com.cn/new/fulltextSearch/full?searchkey%E5%B0%81%E9%97%AD%E5%BC%8F%E5%9F%BA%E7%A1%80%E8%AE%BE%E6%96%BD%E8%AF%81%E5%88%B8%E6%8A%95%E8%B5%84%E5%9F%BA%E9%87%91%E6%8B%9B%E5%8B%9F%E8%AF%B4%E6%98%…

智慧校园综合解决方案:提供全方位的学校管理支持

在当今数字化时代&#xff0c;学校管理面临着越来越多的挑战&#xff0c;包括学生管理、教职员工管理、校园安全等。为了应对这些挑战&#xff0c;智慧校园综合解决方案应运而生。智慧校园综合解决方案融入了先进的信息技术&#xff0c;为学校带来了一场管理与教育模式的革新。…

主流电商平台API接口【京东商品详情按关键字搜索商品按图搜索京东商品(拍立淘)获得店铺的所有商品】

主流电商平台商品接口在电商企业中的应用可以帮助企业实现信息同步、库存管理、订单处理、数据分析和营销推广等多重功能&#xff0c;提升运营效率、优化用户体验&#xff0c;进而推动业务增长。 jd API 接入说明 API地址:申请调用KEY地址 调用示例&#xff1a; 参数说明 通用…

jenkins中配置参数化,并在python脚本接收参数实现参数化执行

在公司内进行Monkey测试脚本集成jenkins时&#xff0c;因为需要指定公司内不同的app进行测试。那么可以有两种方法解决该问题&#xff0c;要么创建多个脚本文件&#xff0c;多个jenkins jobs&#xff0c;进行构建。要么可以在配置job时通过传参数的方式&#xff0c;在python脚本…

什么牌子的灯好不伤眼?带你了解什么灯对眼睛伤害最小

眼睛是人类获取信息最重要的感官器官之一&#xff0c;而近视则会导致视力模糊&#xff0c;进而影响学习效果和生活品质。因此&#xff0c;什么灯对眼睛伤害最小成为许多人迫切寻找的目标。本文将为各位家长解答目前许多家长选择为孩子保护视力的产品——护眼台灯。护眼台灯以其…

MAC安装idea教程

https://blog.csdn.net/qq_41810415/article/details/131649541 JDK安装 https://blog.csdn.net/dddebug/article/details/121713633 2、编辑.bash_profile文件 vim .bash_profile 进入编辑模式后 按 i 开始添加内容 结束编辑模式按 ESC 结束后保存输入 :wq! 不保存输入 :q! 检…

数据库原理(关系数据库规范化理论)——(4)

一、关系模式规范化的必要性 1.关系可能出现的问题 数据冗余大&#xff1b;插入异常&#xff1b;删除异常&#xff1b;更新异常&#xff1b; 2.关系模式应满足的基本要求 元组的每个分量必须是不可分割的数据项&#xff1b;数据库中的数据冗余应尽可能少&#xff1b;不要出…

新手教学系列-​​​​​​基础知识(SSH使用)

基础知识(SSH使用) 什么是ssh Secure Shell(安全外壳协议,简称SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境[1]。SSH通过在网络中创建安全隧道来实现SSH客户端与服务器之间的连接[2]。虽然任何网络服务都可以通过SSH实现安全传输,SS…

电商API接口是什么意思?有什么作用?

电商API接口是电子商务领域中一种技术解决方案&#xff0c;它允许不同的软件系统之间进行交互和数据交换。 在电商场景下&#xff0c;电商API接口可以实现的功能非常丰富&#xff0c;例如&#xff1a; 商品管理&#xff1a;获取商品列表、商品详情、搜索商品、上下架商品等&a…

欢迎莅临ARCHE-2024,共享智慧档案盛宴!

敬邀参观&#xff01;2024第三届上海国际智慧档案展览会&#xff08;ARCHE-2024&#xff09;将于2024年6月19-21日在上海跨国采购会展中心盛大开幕。深圳市铨顺宏科技有限公司将展示最新的智慧档案技术与解决方案&#xff0c;展位号H010-H011。期待您的光临&#xff0c;共同探索…

大数据同步方案怎么选,才能提高企业的业务效率?

大数据同步通常指的是在多个数据源或存储系统之间同步数据的过程&#xff0c;可以确保数据的一致性&#xff0c;提高数据的可用性和可靠性&#xff0c;同时支持数据分析和决策制定。 大数据同步的步骤通常包括&#xff1a; 数据识别&#xff1a;确定需要同步的数据类型和范围&…

游泳耳机哪个牌子好性价比高?精选高性价比的四大游泳耳机!

在现代社会中&#xff0c;随着健身和水中运动的普及&#xff0c;游泳耳机作为一种关键的健身配件&#xff0c;正日益受到广泛关注和需求。无论是在游泳池畅游还是深潜海底&#xff0c;好的游泳耳机不仅能提供高品质的音乐享受&#xff0c;更能保护耳朵免受水压和湿润环境的侵害…