树形DP杂练
洛谷 P1352 没有上司的舞会
emmm,树形DP模板题属于是
状态:
表示以 为根的子树中, 选与不选,可以获得的最大欢乐度。
转移:
嗯,顺便放一个树形DP模板
有项边版本:
void dfs(int u)
{
初始状态
for(int i = head[u]; i; i = nxt[i])
{
int v = to[i];
dfs(v);
转移
}
}
无向边版本:
void dfs(int u, int fa)
{
for(int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if(v == fa) continue;
dfs(v, u);
转移
}
return;
} //通常初始化在main
洛谷 P2014 [CTSC1997] 选课
很好的树形DP练手题,其实就是把背包放到了树上。
原本采用 表示以 为根节点的子树,对于前 个节点选 门课的方案数。
既然他是背包,那就采用背包传统降维策略,将状态改为 表示对于 的子树,最多选 门课的方案数。
其次就是DP的顺序,如果第一重循环顺序枚举的话,到后面的时候, 就会覆盖了 ,就导致重复,所以我们倒序枚举。
同时,还需要注意的是在遍历到 这个节点是, 实际上是不会被选到的。
#include<bits/stdc++.h>
using namespace std;
const int N = 301;
int head[N], edge[2 * N], to[2 * N], nxt[2 * N], siz[N];
int f[N][N];
int n, m, tot, ans;
void add(int x, int y)
{
nxt[++tot] = head[x];
head[x] = tot;
to[tot] = y;
}
void dfs(int u)
{
for(int i = head[u]; i; i=nxt[i])
{
int v = to[i];
dfs(v);
for(int j = m + 1; j >= 1; j--) //为什么倒着?
{
for(int k = 0; k < j; k++)
{
f[u][j] = max(f[u][j], f[v][k] + f[u][j - k]); //为什么不选?
}
}
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
{
int v, w;
cin >> v >> w;
f[i][1] = w;
add(v, i);
}
dfs(0);
cout << f[0][m + 1];
return 0;
}
洛谷 P2016 战略游戏
和下一道题相似,这道题是点对线,下一题是点对点
#include<bits/stdc++.h>
using namespace std;
int n, tot;
const int N = 1610;
int to[2 * N], nxt[2 * N], head[N];
int f[N][2];
void add(int u, int v)
{
to[++tot] = v;
nxt[tot] = head[u];
head[u] = tot;
}
void dfs(int u, int fa)
{
f[u][1] = 1;
for(int i = head[u]; i; i = nxt[i])
if(to[i] != fa)
{
int v = to[i];
dfs(v, u);
f[u][0] += f[v][1];
f[u][1] += min(f[v][0], f[v][1]);
}
}
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
int u, v, k;
cin >> u >> k;
for(int j = 1; j <= k; j++)
{
cin >> v;
add(u, v), add(v, u);
}
}
dfs(0, -1);
cout << min(f[0][0], f[0][1]) << endl;
return 0;
}
洛谷 P2458 [SDOI2006]保安站岗
算是这个阶段稍微难一点的一道题。
表示对于节点 看守它的是它自己/它的子节点/它的父节点。
转移根据状态模拟即可。
值得注意的是 +=
和 =
的区别。
主要看递归的顺序。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1510;
int f[N][3]; //0-自己监视 1-子节点监视 2-父节点监视
int k[N], fa[N];
int head[N], edge[2 * N], to[2 * N], nxt[2 * N];
int n, u, v, m, tot;
void add(int x, int y)
{
nxt[++tot] = head[x];
head[x] = tot;
to[tot] = y;
}
void dfs(int u)
{
f[u][0] = k[u];
for(int i = head[u]; i; i = nxt[i])
{
int v = to[i];
dfs(v);
f[u][0] += min(f[v][0], min(f[v][1], f[v][2])); //f[i][0] 如果自己放,那么子节点放不放都无所谓
f[u][2] += min(f[v][0], f[v][1]); //f[i][2] 如果父节点放,放在子节点或者子节点的子结点上
}
int sum = 0;
f[u][1] = 1e18;
for(int i = head[u]; i; i = nxt[i])
{
int v = to[i];
sum += min(f[v][0], f[v][1]); //求子节点的最小经费之和
}
for(int i = head[u]; i; i = nxt[i])
{
int v = to[i];
f[u][1] = min(f[u][1], sum - min(f[v][0], f[v][1]) + f[v][0]);
}
//选择一个最优的子节点放,既然父节点由子节点监视,那么父节点就没有人监视其他子节点,其他子节点就需要自己监视或者靠其他子节点的子节点监视
}
signed main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> u;
cin >> k[u] >> m;
for(int j = 1; j <= m; j++)
{
cin >> v;
add(u, v);
fa[v]++;
}
}
for (int i = 1; i <= n; i++)
if(!fa[i]) //找到根
{
dfs(i);
cout << min(f[i][0], f[i][1]); //根节点没有父节点
break;
}
return 0;
}
AcWing 1078.旅游规划
虽然这是一棵树,但是它的边是没有方向的,因此吗,每个点都可以作为根,可是如果直接这样求直径的话,肯定会超时,因此我们换一种方法。
我们任取一个节点作为根,因为是直径,它有且仅有一个深度最小的节点,当然,我们不用考虑这个节点是不是根节点。
我们可以通过两次 求解,第一次 从叶子节点搜向根节点,求出每个节点向下的两个最长路径,将两个路径的长度加和后打擂台取 就可以得到直径的长度。
处理完直径后,剩下的就是直径中的点。
我们可以按照前面的方法,对于每一个节点,判断它的最大的两个路径之和是否等于直径的长度,由于第一次 是处理的都是向下的路径,所以我们第二遍 要从根节点搜向叶子结点,处理每个节点的向上的最大路径。
在下面是核心代码:
其中 分别为下边的两条最大路径, 为下边路径的过程, 为上边的最大路径
(注:因为有可能有多个子节点,所以求两个下边的最大路径。而每个节点只有一个父节点,所以只求一个上边的最大路径)
找下面的路径(先处理子节点):
void ddfs(int u, int fa)
{
for(int i = head[u]; i; i = nxt[i]) //找下面的边,实际上是从下往上
{
int v = to[i];
if(v == fa) continue;
ddfs(v, u); //先处理子节点
if(d1[v] >= d1[u]) //处理最大值和次大值
{
d2[u] = d1[u], d1[u] = d1[v] + 1;
res[u] = v;
}
else if(d1[v] >= d2[u]) d2[u] = d1[v] + 1;
}
maxn = max(maxn, d1[u] + d2[u]);
return;
}
找上面的路径(后处理子节点):
void udfs(int u, int fa) //找上面的边,实际上是从上往下
{
for(int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if(v == fa) continue;
up[v] = up[u] + 1;
if(v == res[u]) up[v] = max(up[v], d2[u] + 1);
//如果当前的路径在向下的最长路径上,up 为向下的次长距离或者到根节点的距离
else up[v] = max(d1[u] + 1, up[v]);
//否则 up 为向下的最长距离或者到根节点的距离
udfs(v, u); //后处理子节点
}
return;
}
完整代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, tot, maxn;
int d1[N], d2[N], res[N], up[N];
int head[N], nxt[2 * N], to[2 * N];
void add(int x, int y)
{
to[++tot] = y;
nxt[tot] = head[x];
head[x] = tot;
}
void ddfs(int u, int fa)
{
for(int i = head[u]; i; i = nxt[i]) //找下面的边,实际上是从下往上
{
int v = to[i];
if(v == fa) continue;
ddfs(v, u); //先处理子节点
if(d1[v] >= d1[u]) //处理最大值和次大值
{
d2[u] = d1[u], d1[u] = d1[v] + 1;
res[u] = v;
}
else if(d1[v] >= d2[u]) d2[u] = d1[v] + 1;
}
maxn = max(maxn, d1[u] + d2[u]);
return;
}
void udfs(int u, int fa) //找上面的边,实际上是从上往下
{
for(int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if(v == fa) continue;
up[v] = up[u] + 1;
if(v == res[u]) up[v] = max(up[v], d2[u] + 1);
//如果当前的路径在向下的最长路径上,up 为向下的次长距离或者到根节点的距离
else up[v] = max(d1[u] + 1, up[v]);
//否则 up 为向下的最长距离或者到根节点的距离
udfs(v, u); //后处理子节点
}
return;
}
int main()
{
cin >> n;
for(int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
add(u, v), add(v, u);
}
ddfs(0, 0); udfs(0, 0);
for(int i = 0; i < n; i++)
{
int t[] = {0, d1[i], d2[i], up[i]};
sort(t, t + 4);
if(t[2] + t[3] == maxn) cout << i << endl;
}
return 0;
}
话说我还有好多题没做呢(
hmm 再来填一下坑坑
bzoj 4987 Tree
首先是 的情况
未完待续……
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!