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;
}

思路

见代码

posted @ 2020-04-26 20:09  刘子闻  阅读(132)  评论(0)    收藏  举报