听说刚刚写的蓝桥最后一题只有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的正整数。
输出格式
输出一个整数,表示学分总数。
由于这个题涉及到树上分组背包,等到我学完分组背包再来追更~
看起来分组背包是个重难点啊!