4.26 刷题Day 1 树形dp两题
P1352 没有上司的舞会
【题型】树形dp
AC代码【1】
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int n;
int head[6005],next[6005],last[6005];
int root;
int f[5][6005];
void dp(int x){
for(int i=last[x];i>=0;i=next[x]){
dp(i);
f[1][x]=max(max(f[1][x],f[0][i]+f[1][x]),f[0][i]);
//选这个点:不选子节点,子节点不选时最大值+自己,只是不选子节点时最大值
f[0][x]=max(max(f[0][x],f[1][i]+f[0][x]),max(f[1][i],f[0][i]));
//不选这个点:可以是自己、子节点也不选,子节点选+自己,仅仅子节点选
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&f[1][i]);
}
for(int i=1;i<=n-1;i++){
int x=0,y=0;
scanf("%d%d",&x,&y);
head[x]++;
next[x]=last[y];
last[y]=x;
}
//找树的根
for(int i=1;i<=n;i++){
if(head[i]==0){
root=i;
break;
}
}
dp(root);
printf("%d",max(f[1][root],f[0][root]));
return 0;
}
思路
从根节点开始,我们对于每一个点\(i\),用\(f[i][0]\)来表示不取\(i\)时的最大值,\(f[i][1]\)表示取\(i\)时的最大值
对于每一个点\(i\),则有状态转移方程:
\[f[i][1]=h[i]+\sum f[j][0]
\]
\[f[i][0]=\sum \max(f[j][1],f[j][0])
\]
其中\(j\)是\(i\)的子节点,值得注意的是,当\(i\)不取的时候,对于\(i\)的子节点\(j\),我们也不一定要取,具体见代码
AC代码【2】
前置知识
struct Edge
{
int nxt,to,val;
}e[6001];//这一步是定义一个结构体。
void add(int a,int b,int value)
{
e[++tot].to = b;//拓展一个新的边
e[tot].val = value;//这是这条边的权值
e[tot].last = st[a];
st[a] = tot;
}
//遍历的代码
for(int st[i];i != -1;i = e[i].last)
{
}
AC代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int n,t,d[6001],h[6001],cnt,head[6001],f[6001][2];
struct Edge
{
int nxt,to,val;
}edge[6001];
void ins(int a,int b)
{
edge[++cnt].nxt=head[a];
edge[cnt].to=b;
head[a]=cnt;
}
void dfs(int x)
{
for(int i=head[x];i;i=edge[i].nxt)
{
int j=edge[i].to;
dfs(j);
f[x][0]+=max(f[j][1],f[j][0]);
f[x][1]+=f[j][0];
}
f[x][1]+=h[x];
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>h[i];
for(int i=1;i<=n;i++)
{
int x,y;
cin>>x>>y;
d[x]++;
if(x!=0&&y!=0)
ins(y,x);
}
for(int i=1;i<=n;i++)
if (d[i]==0)
t=i;
dfs(t);
printf("%d",max(f[t][0],f[t][1]));
}
思路
首先定义一个f数组,为f[i][1]和f[i][2]. f[i][1]里面存的是,假如现在的这个人不去,那么他的下属就可以去, 可以得到动态转移方程$$f[i][1] =\sum(\max(f[j][2],f[j][1]))$$
如果这个人要去,那么就存于f[i][2]了,可得: $$f[i][2] =\sum(f[j][1]) + a[i]$$
P1270 “访问”美术馆
【题型】树形dp
AC代码
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
using namespace std;
int t, n, m, dp[1005][605];
//dp[i][j]表示到第i个走廊,在这里(及其子树)要用j秒时间偷的最多画数
struct node {
int tim, pic; //tim表示走廊时间,pic表示图片个数
}tr[5005];
inline int read() {
int red = 0, f_f = 1; char ch = getchar();
while(ch>'9' || ch <'0') {if(ch=='-') f_f = -1; ch = getchar();}
while(ch>='0' &&ch<='9') red = red*10+ch-'0', ch = getchar();
return red*f_f;
}
void dfs_read(int a) { //读入基本差不多
tr[a].tim = read() * 2, tr[a].pic = read();
//走廊要走肯定是来回走的,所以读入时直接*2就避免后面这么多个*2了
if(!tr[a].pic) {
dfs_read(a << 1); dfs_read(a << 1| 1);
}
}
void dfs(int a, int r) {
if(dp[a][r] || !r) return;
//注意
if(tr[a].pic) { //已经到最里面了
dp[a][r] = min(tr[a].pic, (r - tr[a].tim) / 5);
//计算到第a个走廊在这里(及他子树)要花费r秒的时间偷的做多画数
return;
}
for (int i = 0; i <= r - tr[a].tim; i++) { //枚举往左用几秒
dfs(a << 1, i); //左边用 i 秒
dfs(a << 1 | 1, r - tr[a].tim - i); //右边用 r-tim-i 秒
//还要减去过a的走廊的时间(已经*过2了
dp[a][r] = max(dp[a][r], dp[a << 1][i] + dp[a << 1 | 1][r - tr[a].tim - i]);
}
}
int main()
{
memset(dp, 0, sizeof(dp));
t = read() - 1; //比警察早走一秒吧
dfs_read(1);
dfs(1, t); //因为没有返回值所以放外面
printf("%d\n", dp[1][t]);
return 0;
}
思路
见代码
要做就做南波万

浙公网安备 33010602011771号