树形DP专练(1)

听说刚刚写的蓝桥最后一题只有70%的数据能过,原来是并查集+树形DP,于是我又去扩展我的知识边界了orz。
先来看看蓝桥的J题:

网络分析

百分之百正确的解法是:

#include<cstdio>
#include<cstring>

const int N = 200010, M = N << 1;

int h[N], e[M], ne[M], idx;
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}//将b串到a的队伍中

int n, m;
int p[N];//爸爸数组
int find(int x)
{
    if(p[x] != x)  p[x] = find(p[x]);
    return p[x];
}//找到x的爸爸

int f[N];//存分数

void dfs(int u, int fa)
{
    f[u] += f[fa];
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];//找到u拴着的所有儿子
        dfs(j, u);
    }
}

int main()
{
    memset(h, -1, sizeof h);

    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n * 2; i ++)  p[i] = i;

    int root = n + 1;
    while(m --)
    {
        int op, a, b;
        scanf("%d%d%d", &op, &a, &b);
        if(op == 1)
        {
            a = find(a), b = find(b);
            if(a != b)
            {
                p[a] = p[b] = root;
                add(root, a);
                add(root, b);
                root ++;
            }
        }
        else
        {
            a = find(a);
            f[a] += b;
        }
    }

    for(int i = n + 1; i < root; i ++)
        if(p[i] == i)  dfs(i, 0);

    for(int i = 1; i <= n; i ++)
        printf("%d ", f[i]);
    return 0;
}

这是代码思路的草稿:
在这里插入图片描述每次连接的两个点都串在一个链式前向星的一条边中,先把分数全部加到父亲节点的头上,最后再下放到每个子节点中,可以做到不重不漏。
满分!good
下面来做树形DP的题目——

例题①

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题意解析
1.给定一颗N个节点组成的树
2.每个节点上可以染3种颜色
3.其中K个节点已染色
要求任意两相邻节点颜色不同,求合法染色方案数。

这题nude树形DP,我们知道,
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int N = 1e5 + 20, mod = 1e9 +7;
int n, k; //n是谷仓数,k是已经染色的谷仓数
int x, y; //从哪里连到哪里
vector<int> g[N];	//记录联通的谷仓
int c[N]; //记录color
ll f[N][4]; //各结点染色123的方案数

inline void dp(int son, int fa){
	if(c[son])//如果儿子的颜色已固定
		f[son][c[son]] = 1;
	else
		f[son][1] = f[son][2] = f[son][3] = 1;	//如果没有固定,则儿子染每种颜色的方案都为1
	for(int i : g[son]){ //i是儿子的联通点
		if(i == fa) continue;
		dp(i, son);
		f[son][1] = f[son][1] * (f[i][2] + f[i][3]) %mod;
		f[son][2] = f[son][2] * (f[i][1] + f[i][3]) %mod;
		f[son][3] = f[son][3] * (f[i][1] + f[i][2]) %mod;
	}

}

inline void solve(){
	scanf("%d%d", &n, &k);
	for (int i = 1; i < n; ++i)
	{
		scanf("%d%d", &x, &y);
		g[x].push_back(y), g[y].push_back(x); //互相联通	
	}
	for (int i = 0; i < k; ++i)
	{
		scanf("%d%d", &x, &y);
		c[x] = y;
	}
	dp(1, 0);
	printf("%lld\n", (f[1][1] + f[1][2] + f[1][3]) % mod);
}

int main(int argc, char const *argv[])
{
	solve();
	return 0;
}

例题②

经典中的经典——没有上司的舞会
传送门

题意解析
1.一家公司有n个员工,编号为1~n。
2.他们的关系就像一棵以校长为根的数,父节点就是子节点的直接上司。
3.每个员工都有一个快乐指数。
4.现在要开一个周年庆典,没有员工愿意和5直接上司参加舞会,问怎样安排能让快乐值最大,求最大值。
5.数据范围:N≤6000

思路
在这里插入图片描述
有点内味了撒~就让我们继续发现它的乐趣吧🧐

#include<bits/stdc++.h>
using namespace std;

const int N = 6e3 + 10;
int n, x, y, root;//n是舞会人数, x是下属,y是上司,root是究极大boss
int h[N]; //happy指数
vector<int> son[N]; //儿子们
int v[N]; //用来找到根结点
int f[N][2]; //记录大家的最大快乐值

inline void dp(int a){
	f[a][0] = 0;//不选爷就没有快乐
	f[a][1] = h[a];//选了爷才有快乐
	for(int s : son[a]){//s是a的儿子们
		dp(s);
		f[a][0] += max(f[s][0], f[s][1]);//如果a没来,s来或者不来都可以
		f[a][1] += f[s][0];//如果a来了,s就不能来
	}
}

inline void solve(){
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &h[i]);
	for (int i = 1; i <= n - 1; ++i)
	{
		scanf("%d%d", &x, &y);
		son[y].push_back(x); //y的儿砸x
		v[x] = 1; //是儿子就要标记一下
	}
	for (int i = 1; i <= n; ++i)
		if(!v[i]){root = i; break;}
	dp(root);
	printf("%d\n", max(f[root][0], f[root][1]));
}

int main(int argc, char const *argv[])
{
	solve();
	return 0;
}

例题③

也不能老是切简单题对不对?
¥¥!来!小二!上难度!👊

学校实行学分制。
每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分。
学校开设了 N 门的选修课程,每个学生可选课程的数量 M 是给定的。
学生选修了这 M 门课并考核通过就能获得相应的学分。
在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其他的一些课程的基础上才能选修。
例如《Windows程序设计》必须在选修了《Windows操作基础》之后才能选修。
我们称《Windows操作基础》是《Windows程序设计》先修课。
每门课的直接先修课最多只有一门。
两门课可能存在相同的先修课。
你的任务是为自己确定一个选课方案,使得你能得到的学分最多,并且必须满足先修条件。
假定课程之间不存在时间上的冲突。
输入格式
输入文件的第一行包括两个整数N、M(中间用一个空格隔开)其中1≤N≤300,1≤M≤N。
接下来N行每行代表一门课,课号依次为1,2,…,N。
每行有两个数(用一个空格隔开),第一个数为这门课先修课的课号(若不存在先修课则该项为0),第二个数为这门课的学分。
学分是不超过10的正整数。
输出格式
输出一个整数,表示学分总数。

由于这个题涉及到树上分组背包,等到我学完分组背包再来追更~
看起来分组背包是个重难点啊!
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值