【牛客题单】动态规划课程树型dp习题
文章目录
NC13249 黑白树
大意:
一棵n个点的有根树,1号点为根,相邻的两个节点之间的距离为1。树上每个节点i对应一个值k[i]。每个点都有一个颜色,初始的时候所有点都是白色的。你需要通过一系列操作使得最终每个点变成黑色。每次操作需要选择一个节点i,i必须是白色的,然后i到根的链上(包括节点i与根)所有与节点i距离小于k[i]的点都会变黑,已经是黑的点保持为黑。问最少使用几次操作能把整棵树变黑。
思路:
首先需要明确,处理某个节点的时候,它的子节点一定都全部染成黑色了
dfs的时候传递的是:操作res-1次时,子节点能染色的最大范围,而操作res次时,染色的最大范围则挂在k数组上更新,这样相当于进行了两个数值的传递
所以当子节点传递上来的范围不包括当前节点时,就需要进行染色,但是不一定是染这个节点,只是选择一个能贡献最大染色范围的节点
但是我们不需要考虑到底是染哪个点,只需要把最大的染色范围更新到当前的k数组即可
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
int n,f[N],res=0;
vector<int> mp[N];
int dfs(int now, int fa) {
int w = 0;
for (int i = 0; i < mp[now].size(); i++) {
int ne = mp[now][i];
if (ne == fa) continue;
w = max(w, dfs(ne, now));
}
if(w<=0){
res++;
return f[now] - 1;
}
f[fa] = max(f[fa], f[now] - 1);
return w-1;
}
int main() {
cin >> n;
for (int i = 2; i <= n; i++) {
int x;
cin >> x;
mp[x].push_back(i);
}
for (int i = 1; i <= n; i++) cin >> f[i];
dfs(1, 0);
cout << res << endl;
return 0;
}
NC15748 旅游
大意:
旅行地图上有n个城市,它们之间通过n-1条道路联通。
Cwbc和XHRlyb第一天会在s市住宿,并游览与它距离不超过1的所有城市,之后的每天会选择一个城市住宿,然后游览与它距离不超过1的所有城市。他们不想住在一个已经浏览过的城市,又想尽可能多的延长旅行时间。XHRlyb想知道她与Cwbc最多能度过多少天的时光呢?
思路:
最大点独立集裸题
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
int n, s,dp[N][2];
vector<int> mp[N];
void dfs(int now, int fa) {
for (int i = 0; i < mp[now].size(); i++) {
int ne = mp[now][i];
if (ne == fa) continue;
dfs(ne, now);
dp[now][1] += dp[ne][0];
dp[now][0] += max(dp[ne][0], dp[ne][1]);
}
dp[now][1]++;
}
int main() {
cin >> n >> s;
for (int i = 0; i < n - 1; i++) {
int x, y;
cin >> x >> y;
mp[x].push_back(y);
mp[y].push_back(x);
}
dfs(s, 0);
cout << dp[s][1] << endl;
return 0;
}
NC19782 Tree
大意:
对于n个节点的一个树中的每一个点,求出包含这个点的点联通集的数量
思路:
换根就要想到把联通集分为两类,一类是这个点为根的子树中的联通集,另一类是不以它为根的
设dp[ i ]为以i为根的子树中包含i这个点的点联通集的数量,ne为i的子节点,那么:
d p [ i ] = ∏ ( d p [ n e ] + 1 ) dp[i]=\prod(dp[ne]+1) dp[i]=∏(dp[ne]+1)
即每个子节点选(dp[ne])或不选(1),然后相乘
设pre[i]为除了i的子树中包含i号点的联通集的数量,假设res[ i ]为每个点的答案,fa为父节点,那么有:
p r e [ i ] = r e s [ f a ] d p [ i ] + 1 pre[i]=\frac{res[fa]}{dp[i]+1} pre[i]=dp[i]+1res[fa]
因为res[ fa ]代表父节点的答案,也就是所有包含父节点的联通集,可以由父节点以上的联通块再乘上i节点选或不选,
即res[fa] =pre[i] * (dp[i]+1),那么直接除一下得到pre[i]
但是是取模意义下的,所以要求逆元
但是如果dp[i]+1对mod取模为0,那么无法直接求逆元来算pre,此时只需要反向考虑,暴力求一下pre即可
总结一下就是务必要搞清楚各个数组的含义,不然很难推出传递关系。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
typedef long long LL;
LL qmi(LL a, LL k, LL p) {
LL res = 1 % p; // res记录答案, 模上p是为了防止k为0,p为1的特殊情况
while (k) {
// 只要还有剩下位数
if (k & 1)
res =
(LL)res * a % p; // 判断最后一位是否为1,如果为1就乘上a,模上p,
// 乘法时可能爆int,所以变成long long
k >>= 1; // 右移一位
a = (LL)a * a %
p; // 当前a等于上一次的a平方,取模,平方时可能爆int,所以变成long
// long
}
return res;
}
LL get_inv(LL a, LL p) {
return