Ⅰ.P3914 染色计数
原题链接:P3914 染色计数 - 洛谷
分析
多限制了颜色,则将其写进状态。设表示u子树,u节点强制染成i颜色的总方案数。
考虑转移,利用乘法原理,我们可以轻松得出:
正解
#include <bits/stdc++.h>
#define ll long long
#define P 1000000007
using namespace std;
const int N = 5005;
int n, m, k, u, v, cnt, f[N][N];
ll S[N];
vector<int> e[N];
void dfs(int u, int fa){
for (auto v : e[u]){
if (v == fa)
continue;
dfs(v, u);
for (int i = 1; i <= m; i++){
f[u][i] = f[u][i] * (S[v] - f[v][i]) % P;
}
}
for (int i = 1; i <= m; i++)
S[u] = (S[u] + f[u][i]) % P;
}
int main(){
cin >> n >> m;
for (int i = 1; i <= n; i++){
cin >> cnt;
for (int j = 1; j <= cnt; j++){
cin >> k;
f[i][k] = 1;
}
}
for (int i = 1; i < n; i++){
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs(1, 0);
cout << (S[1] + P) % P; // 避免负数
}
Ⅱ.P13020 [GESP202506 八级] 遍历计数
原题链接:P13020 [GESP202506 八级] 遍历计数 - 洛谷
分析
设表示u子树内的dfn数量,更新为:
初始化,
答案,
但是,复杂度是的。
我们分析一下过程,发现每个点对答案的贡献就是它子节点数量的阶乘。根节点子节点数量就是其度数,其余节点是度数-1。设根节点为,
为各节点的度数,那么
然后预处理前缀积Pre以及后缀积Suf,
正解
#include <bits/stdc++.h>
#define mod 100000000
using namespace std;
const int N = 100005;
int n, u, v;
int d[N];
long long Pre[N], Suf[N], fac[N];
int main(){
cin >> n;
for (int i = 1; i < n; i++){
cin >> u >> v;
d[u]++;
d[v]++;
}
fac[0] = 1;
for (int i = 1; i <= n; i++){
fac[i] = fac[i - 1] * i % mod;
}
Pre[0] = 1;
for (int i = 1; i <= n; i++){
Pre[i] = Pre[i - 1] * fac[d[i] - 1] % mod;
}
Suf[n + 1] = 1;
for (int i = n; i >= 1; i--){
Suf[i] = Suf[i + 1] * fac[d[i] - 1] % mod;
}
long long ans = 0;
for (int u = 1; u <= n; u++)
ans = (ans + Pre[u - 1] * Suf[u + 1] % mod * fac[d[u]]) % mod;
cout << ans;
}
Ⅲ.AT_abc259_f [ABC259F] Select Edges
原题链接1:AT_abc259_f [ABC259F] Select Edges - 洛谷
原题链接2:F - Select Edges
分析
学到这其实是很自然的了,设表示u子树,选择了i条与u相连的边的最大边权和。
但是,u到u的父亲还有一条边,我们考虑到加上这条边只会让i增加1,那么我们就可以简化状态,我们设表示u子树,已选边小于/小于等于
的最大边权和。
考虑转移,
①不选边(u,v):v子树贡献
②选边(u,v):v子树贡献
实现
先跑①,在更新②
正解
#include <bits/stdc++.h>
using namespace std;
const int N = 300005;
int n, d[N], u, v, w;
long long f[N][2];
vector<pair<int, int>> e[N];
void dfs(int u, int fa){
vector<long long> b;
for (auto tmp : e[u]){
int v = tmp.first, w = tmp.second;
if (v == fa)
continue;
dfs(v, u);
f[u][0] += f[v][1];
b.push_back(w + f[v][0] - f[v][1]);
}
f[u][1] = f[u][0];
sort(b.begin(), b.end(), greater<long long>());
int cnt = 0;
for (auto x : b){
++cnt;
if (cnt < d[u] && x > 0)
f[u][0] += x;
if (cnt <= d[u] && x > 0)
f[u][1] += x;
}
if (d[u] == 0)
f[u][0] = 0xc0c0c0c0c0c0c0c0;
}
int main(){
cin >> n;
for (int i = 1; i <= n; i++)
cin >> d[i];
for (int i = 1; i < n; i++){
cin >> u >> v >> w;
e[u].push_back({v, w});
e[v].push_back({u, w});
}
dfs(1, 0);
cout << f[1][1];
}
树形DP一般设计成的形式,u表示u子树内,state表示多余限制
Ⅳ.P3174 [HAOI2009] 毛毛虫
原题链接:P3174 [HAOI2009] 毛毛虫 - 洛谷
分析
题目形式化为对于一棵树,找到一条链使得链上节点及其相邻节点总和最大。
那么,我们设表示以u子树内以u为一端点的最大毛毛虫的大小,
考虑转移,(加u本身,减
和u父亲,其中mx1是最大的儿子)。
那么u节点处的答案就是(其中,mx1和mx2分别是最大值和次大值)
似懂非懂……
正解
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 300005;
int n, m, k, s, t, f[N], ans = 1;
vector<int> e[N];
void dfs(int u, int fa){
if (u != 1 && e[u].size() == 1){ //特判叶子
f[u] = 1;
return ;
}
int mx1 = 0, mx2 = 0;
for (auto v : e[u]){
if (v == fa)
continue;
dfs(v ,u);
if (f[v] >= mx1){
mx2 = mx1;
mx1 = f[v];
}
else if (f[v] > mx2)
mx2 = f[v];
}
ans = max({ans, mx1 + (int)e[u].size(), mx1 + mx2 + (int)e[u].size() - 1});
f[u] = mx1 + e[u].size() - 1;
}
int main(){
cin >> n >> m;
for (int i = 1; i <= m; ++i){
cin >> s >> t;
e[s].push_back(t);
e[t].push_back(s);
}
dfs(1, 0);
cout << ans;
}
Ⅴ.P6147 [USACO20FEB] Delegation G
原题链接:P6147 [USACO20FEB] Delegation G - 洛谷
分析
我们发现,只有时才成立。那么,最多有
中可能。
设表示u子树内已经存在了若干长度为K的链,剩余一条以u为端点的链的长度(可证长度确定)。
实现
1.先遍历u的所有子节点找出剩余的不能组合成K的链,尽可能配对
2.再遍历u的所有子节点看是否最多剩余一条链(若超过1条就无解了)
正解
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 100005;
const int mod = 1e9 + 7;
int n, u, v, K, ans, f[N], cnt[N]; // f[u]:u子树正常划分后,当前链长度
vector<int> e[N];
void dfs(int u, int fa){
if (u != 1 && e[u].size() == 1){ // 特判叶节点
f[u] = 0;
return;
}
for (auto v : e[u]){
if (v == fa) continue;
dfs(v, u); // 计算f[v]
}
for (auto v : e[u]){
if (v == fa || f[v] + 1 == K)
continue;
if (cnt[K - (f[v] + 1)]) //和当前链匹配的长度存在
--cnt[K - (f[v] + 1)];
else
++cnt[f[v] + 1];
}
f[u] = 0;
for (auto v : e[u]){
if (v == fa || f[v] + 1 == K)
continue;
if (cnt[f[v] + 1] == 1){ //发现一条链
if (f[u])
ans = 0; //之前有别的剩余链,无解
f[u] = f[v] + 1; //更新剩余链长度
--cnt[f[v] + 1]; //从桶中移除该链的计数,避免重复处理
}
if (cnt[f[v] + 1] > 1) //不止一条该长度剩余链
ans = 0; //无解
}
}
int main(){
cin >> n;
for (int i = 1; i < n; ++i){
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
cout << 1;
for (int i = 2; i <= n - 1; ++i)
if ((n - 1) % i == 0){
memset(cnt, 0, sizeof(cnt));
K = i; ans = 1;
dfs(1, 0);
cout << (ans && (f[1] == 0));//可行对应ans等于1并且f[1]等于0
}
else cout << 0;
}
Ⅵ.P6142 [USACO20FEB] Delegation P
原题链接:P6142 [USACO20FEB] Delegation P - 洛谷
分析
区别于上一题的恰好是K,这题要求至少是K,那么我们看似失去了一些约束,但是考虑到K越小似乎越容易满足!那就二分答案!
正解
#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, ans, res, f[N], K; // f[u] 记录节点u向上传递的未匹配的链长度,res 标记是否可行,K 是当前二分的中间值
vector<int> e[N]; // 邻接表存储树的结构
// 深度优先搜索函数,u是当前节点,fa是父节点
void dfs(int u, int fa){
multiset<int> ms; // 用于存储子节点向上传递的链长度
for (auto v : e[u]){ // 遍历当前节点的所有子节点
if (v == fa) continue; // 跳过父节点
dfs(v, u); // 递归处理子节点
ms.insert(f[v] + 1); // 将子节点向上传递的链长度加1后插入集合
}
f[u] = 0; // 初始化当前节点向上传递的链长度为0
// 如果当前节点不是根节点且子节点数量为偶数,插入0(用于处理匹配)
if (u != 1 && ms.size() % 2 == 0)
ms.insert(0);
while (!ms.empty()){ // 当集合不为空时进行匹配
int x = *ms.begin(); // 取出集合中的最小值
ms.erase(ms.begin()); // 删除该最小值
// 二分查找集合中是否存在大于等于K - x的元素
auto y = ms.lower_bound(K - x);
if (y == ms.end()){ // 如果不存在这样的元素
if (f[u] > 0){ // 如果当前节点已经有未匹配的链长度
res = 0; // 标记为不可行
return;
}
f[u] = x; // 将x作为当前节点向上传递的未匹配链长度
}
else{
ms.erase(y); // 存在则删除该元素,完成匹配
}
}
}
// 检查函数,判断是否可以划分出每条链长度至少为mid的链
bool check(int mid){
res = 1; // 初始化为可行
K = mid; // 设置当前检查的长度
dfs(1, 0); // 从根节点开始深度优先搜索
// 如果可行且根节点向上传递的链长度为0或者大于等于K,返回true
return (res == 1 && (f[1] == 0 || f[1] >= K));
}
int main(){
cin >> n; // 输入节点数量
for (int i = 1; i < n; i++){ // 输入n - 1条边
int u, v;
cin >> u >> v;
e[u].push_back(v); // 建立邻接表
e[v].push_back(u);
}
int l = 1, r = n - 1, mid; // 二分查找的左右边界
while (l <= r){ // 二分查找过程
mid = (l + r) / 2;
if (check(mid)){ // 如果当前mid可行
ans = mid; // 更新答案
l = mid + 1; // 尝试更大的长度
}
else{
r = mid - 1; // 尝试更小的长度
}
}
cout << ans; // 输出最大的K
return 0;
}
路径相关的树形DP,看成直上直下的链!再合并!
1327

被折叠的 条评论
为什么被折叠?



