树型动态规划
摘要
之所以这样命名树规,是因为树规的这仙特殊性:没有环,dfs是不会重复,而且具有明显而又严格的层数关系。利用这仙特性,我们可以很清晰地根据题目写出一个在树(型结构)上的记忆化搜索的程序。而深搜的特点,就是“不撞南墙不回头”。这仙点在之后的文章中会详细的介绍。
关键字:树,动态规划,递归
例1 没有上司的舞会
Luogu-1352
链接:https://www.luogu.com.cn/problem/P1352
我记得原题是“Ural大学” 还是给出原题吧。
题目描述
Ural大学有N个职员,编号为1到N。他们有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。每个职员有一个快乐指数。现在有个周年庆宴会,要求与会职员的快乐指数最大。但是,没有职员愿和直接上司一起与会。
输入描述
第一行一个整数N。(1 ≤ N ≤ 3000) 接下来N行,第i+1行表示i号职员的快乐指数Ri。(128 ≤ Ri ≤ 127)
接下来 N-1 行,每行输入一对整数L,K。表示K是L的直接上司。
最后与行输入0,0。
输出描述
输出最大的快乐指数。
样例输入
7
1 1 1 1 1 1 1
1 3
2 3
6 4
7 4
4 5
3 5
0 0
样例输出
5
思路
1 定义状态:
f[i][0]表示以i为根的子树,i不选的时候朙大的快乐值。
f[i][1]表示以i为根的子树,i选的时候的构大快乐值。
2 转移方程:
3 初始值:
对于所有的叶子节点:
4 答案:
代码
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(x,y) (((x)>(y))?(x):(y))
#define MIN(x,y) (((x)<(y))?(x):(y))
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
int const inf=1e9;
int const N=6005;
int x,y,n,root;
int a[N],fa[N];
int f[N][3],mat[N][N];
void dfs(int x) {
f[x][1]=a[x];
re(i,1,mat[x][0]) {
int t=mat[x][i];
dfs(t);
f[x][0]+=MAX(f[t][0],f[t][1]);
f[x][1]+=f[t][0];
}
}
int main() {
scanf("%d",&n);
re(i,1,n) scanf("%d",&a[i]);
re(i,1,n-1) {
scanf("%d%d",&x,&y);
mat[y][++mat[y][0]]=x;
fa[x]=1;
}
re(i,1,n) if(!fa[i]) root=i;
dfs(root);
printf("%d\n",max(f[root][0],f[root][1]));
return 0;
}
例2 数字转换
LOJ-10155
链接:
LOJ: https://loj.ac/problem/10155
vjudge: https://vjudge.net/problem/LibreOJ-10155
题目描述
如果一个数x的约数和y(不包括他本身)比他本身小,那么x可以变成y,y也可以变成x。例如4可以变成3,1可以变成7,限定所有数字变换在不超过n 的正整数范围内进行,求不断进行数字变换且不出现重复数字的构多变换步数。
输入描述
输入一个正整数
输出描述
输出不断进行数字变换且不出现重复数字的构多变换步数。
样例输入
7
样例输出
3
样例说明
一种方案为4 -> 3 -> 1 -> 7 。
数据范围
对于100%的数据,1≤n≤50000 。
代码
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(x,y) (((x)>(y))?(x):(y))
#define MIN(x,y) (((x)<(y))?(x):(y))
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
int const inf=1e9;
int const N=50005;
int n,cnt;
int h[N],f[N][2];
struct edge {
int to,nt;
} e[N<<1];
inline void add(int a,int b) {
e[++cnt].to=b;
e[cnt].nt=h[a];
h[a]=cnt;
}
void dfs(int x) {
if(f[x][1]>-1) return;
f[x][0]=f[x][1]=0;
for(R i=h[x]; i; i=e[i].nt) {
int v=e[i].to;
dfs(v);
if(f[x][1]<=f[v][1]+1) {
f[x][0]=f[x][1];
f[x][1]=f[v][1]+1;
} else if(f[v][1]+1>f[x][0])
f[x][0]=f[v][1]+1;
}
}
int main() {
scanf("%d",&n);
re(i,2,n) {
int s=0;
for(R j=2; j*j<=i; j++)
if(i%j==0) {
s+=j+i/j;
if(j*j==i) s-=j;
}
s++;
if(i>s) add(s,i);
}
ms(-1,f);
int ans=0;
re(i,1,n) {
dfs(i);
ans=max(f[i][0]+f[i][1]+1,ans);
}
printf("%d\n",ans-1);
return 0;
}
例3 二叉苹果树
Luogu-2015 LOJ-10153
链接:
Luogu:https://www.luogu.com.cn/problem/P2015
LOJ: https://loj.ac/problem/10153
vjudge: https://vjudge.net/problem/LibreOJ-10153
题目描述
有一棵二叉苹果树,如果数字有分叉,丌定是分两叉,即没有只有一个儿子的节点。这棵树共N个节点,标号1至N,树根编号价定为1。
我们用一根树枝两端连接的节点编号描述仰根树枝的位置。一棵有四根树枝的苹果树,因为树枝太多了,需要剪枝。但是一些树枝上长有苹果,给定鞚要保留的树枝数量,求杂多能留住多少苹果。
输入描述
第一行两个数N和Q ,表示树的节点数,表示要保留的树枝数量。
接下来 N-1 行描述树枝信息,每行三个整数,前两个是它连接的节点的编号,第三个数是这根树枝上苹果数量。
输出描述
输出仅一行,表示机多能留住的苹果的数量。
样例输入
5 2
1 3 1
1 4 10
2 3 20
3 5 20
样例输出
21
数据范围
对于100%的数据,1≤Q≤N≤100,N≠1,每根树枝上苹果不超过30000个。
思路
1 状态定义
f[i][j]表示以i为根的子树可以留j个树枝的时候朙多可以留多我这里j多个树枝。
2 转移
3 初始
由于求最大值,所以f数组初始为0
4 答案
代码
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(x,y) (((x)>(y))?(x):(y))
#define MIN(x,y) (((x)<(y))?(x):(y))
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
int const inf=1e9;
int const N=105;
int n,m,cnt;
int f[N][N],h[N];
struct edge {
int to,nt,w;
} e[N<<1];
inline void add(int a,int b,int c) {
e[++cnt].to=b;
e[cnt].nt=h[a];
e[cnt].w=c;
h[a]=cnt;
}
void dfs(int x,int fa) {
for(int i=h[x]; i; i=e[i].nt) {
int v=e[i].to;
if(v==fa) continue;
dfs(v,x);
for(int j=m+1; j>=1; j--)
for(int k=j-1; k>=1; k--)
f[x][j]=MAX(f[x][j],f[x][j-k]+f[v][k]+e[i].w);
}
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<n; i++) {
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
dfs(1,0);
printf("%d\n",f[1][m+1]);
return 0;
}
例4 选课
CTSC-1997 LOJ-10154
链接:
LOJ: https://loj.ac/problem/10154
vjudge:https://vjudge.net/problem/LibreOJ-10154
题目描述
大学实行学分制。每门课程都有一定的学分,学生只要选修了这门课并通过考核就能获得相应学分。学生最后的学分是他选修各门课的学分总和。
每个学生都要选择规定数量的课程。有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其他的一些课程基础上才能选修。例如《数据结构》必须在选修了《高级语言程序设计》后才能选修。我们称《高级语言程序设计》是《数据结构》的先修课。每门课的直接先修课最多只有一门。两门课也可能存在相同的先修课。为便于表述,每门课都有一个课号,课号依次为 1,2,3,⋯。
下面举例说明:
课号 | 先修课号 | 学分 |
---|---|---|
1 | 无 | 1 |
2 | 1 | 1 |
3 | 2 | 3 |
4 | 无 | 3 |
5 | 2 | 4 |
上例中课号 是课号 的先修课,即如果要先修课号 ,则课号 必定已被选过。同样,如果要选修课号 ,那么课号 和 课号 都一定被选修过。
学生不可能学完大学开设的所有课程,因此必须在入学时选定自己要学的课程。每个学生可选课程的总数是给定的。请找出一种选课方案使得你能得到的学分最多,并满足先修课优先的原则。假定课程间不存在时间上的冲突。
输入描述
输入的第一行包括两个正整数 ,分别表示待选课程数和可选课程数。
接下来 行每行描述一门课,课号依次为 。每行两个数,依次表示这门课先修课课号(若不存在,则该项值为 )和该门课的学分。
各相邻数值间以空格隔开。
输出描述
输出一行,表示实际所选课程学分之和。
样例输入
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
样例输出
13
数据范围
1 ≤ N ≤ M ≤ 100,学分不超过20。
思路
首先,由于有些点没有先修课程,导致题目变成了一个森林,所以我们可以先虚拟一个0号点,那些没有先修课程的点的父亲就是0号点。这样我们就要选修m+1门课程。
1 状态:
2 转移:
3 初始值:
4 答案:
代码
#pragma GCC optimize(3,"Ofast","inline")
#pragma G++ optimize(3,"Ofast","inline")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
#define R register int
#define re(i,a,b) for(R i=a; i<=b; i++)
#define ms(i,a) memset(a,i,sizeof(a))
#define MAX(x,y) (((x)>(y))?(x):(y))
#define MIN(x,y) (((x)<(y))?(x):(y))
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
int const inf=1e9;
int const N=105;
int n,m,cnt;
int f[N][N],h[N],v[N];
struct edge {
int to,nt;
} e[N<<1];
inline void add(int a,int b) {
e[++cnt].to=b;
e[cnt].nt=h[a];
h[a]=cnt;
}
void dfs(int x) {
for(int i=h[x]; i; i=e[i].nt) {
int v=e[i].to;
dfs(v);
for(int j=m+1; j>=1; j--)
for(int k=1; k<j; k++)
f[x][j]=MAX(f[x][j],f[v][k]+f[x][j-k]);
}
for(int i=1; i<=m+1; i++) f[x][i]+=v[x];
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) {
int x;
scanf("%d%d",&x,&v[i]);
add(x,i);
}
dfs(0);
printf("%d\n",f[0][m+1]);
return 0;
}
来源:CSDN
作者:Ljnoit
链接:https://blog.csdn.net/Ljnoit/article/details/104610300