树形dp 围绕某个点为根节点
树形dp 选不选择根节点的问题
没有上司的舞会
对于根节点的子集 都有选择或者不选择的两种情况
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 6010;
int n;
int h[N], e[N], ne[N], idx;
int happy[N];
int f[N][2];
//f[u][1] i表示以i为根节点而且包含了i的总快乐指数
//f[u][0] i表示以i为根节点并且不含i的总快乐指数
bool has_fa[N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)//搜索 从根节点 往下找 然后return 回来
{
f[u][1] = happy[u];//选的话 首先这个值会等于本身
for (int i = h[u]; ~i; i = ne[i])
{//遍历以这个根节点为头结点的 所有子结点
int j = e[i];
dfs(j);//搜索
f[u][1] += f[j][0];//对于选择的 那么子结点一点不可以选择 直接加上 子结点的数字
f[u][0] += max(f[j][0], f[j][1]);
//对于这个根节点 因为没有选上 所以可以选 可以不选
}
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &happy[i]);
memset(h, -1, sizeof h);//memset 所有头节点指向-1
for (int i = 0; i < n - 1; i ++ )
{
int a, b;
scanf("%d%d", &a, &b);
add(b, a);//在图里是 b指向a的 在树里就是根节点指向他
has_fa[a] = true;//有父亲
}
int root = 1;
while (has_fa[root]) root ++ ;//root相当于 idx 只要这个点有父亲说明不是根节点 那么就向后找
dfs(root);//找到了就搜
printf("%d\n", max(f[root][0], f[root][1]));//输出根节点选择或不选择的最大值
return 0;
}
满二叉树的等长路径https://www.acwing.com/solution/content/99455/
给我们一颗满二叉树每条边有长度,现在想要让根到每个叶子节点的长度相同,我们可以随意的对一条边增加长度
问我们最少增加多少长度,可以使根到每个叶子节点的长度相同.
解:
不对u连着的两条边操作时,u到两个子树中的叶节点的路径长度分别是
x:左子树到叶节点的长度+左子树到u的长度,y:右子树到叶节点的长度+右子树到u的长度
我们现在只要让这两个长度相等即可,所以对答案的贡献是abs(x-y)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2050;
int n;
int w[N];//w[i]表示i的父节点到i的距离
int ans;
int dfs(int u)//返回的是每个点到达叶子的距离
{
if (u * 2 > (1 << n + 1) - 1) return 0;
int l = dfs(u * 2) + w[u * 2];//左儿子到叶子节点的 加上左儿子的距离
int r = dfs(u * 2 + 1) + w[u * 2 + 1];
ans += abs(l - r);
return max(l, r);//返回最大的距离 (只能增大)
}
int main()
{
cin >> n;
for (int i = 2; i <= (1 << n + 1) - 1; i ++ ) cin >> w[i];
dfs(1);
cout << ans << endl;
return 0;
}
生命之树
查看每个连通块的最大值
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 1e5+10;
LL f[N];
int g[N];
vector<int>e[N];
#define pb push_back
void dfs(int u,int fa){
f[u]=g[u];
for (int j : e[u] ){
if(j!=fa){
dfs(j,u);//查看每个连通块
f[u]+=max(0ll,f[j]);//
}
}
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> g[i];
for (int i = 1; i <= n-1; i ++ ){
int a,b;cin>>a>>b;
e[a].pb(b);
e[b].pb(a);
}
dfs(1,-1);
LL res=f[1];
for (int i = 2; i <= n; i ++ ){
res=max(res,f[i]);
}
cout << res;
return 0;
}
gk的树
给出边和 点最大度数k 要求删减一些边 让每个边的边达到最小
思路:对于都超过k的相邻点一定删去她们中间的点最优
正着删除 需要排序各种问题
反着来可以 : 删除所有应该删除的边 在+上对此删除的在两个超k个度数的边的边
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int h[N],ne[N*2],e[N*2],idx;
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
int d[N];
int n,k;
int res=0;
void dfs(int u,int fa){
for(int i=h[u]; ~i; i=ne[i]){
int j=e[i];
if(j==fa) continue ;
dfs(j ,u );
if(d[j]>k&&d[u]>k){
res--;//发现删重复就补充上
d[j]--;
d[u]--;
}
}
}
signed main(){
int t;cin>>t;
while(t--){
memset(h ,-1,sizeof h);
memset(d, 0,sizeof d);
idx=0,res=0;
cin>>n>>k;
for(int i=0;i<n-1;i++){
int a,b;cin>>a>>b;
add(a,b),add(b,a);
d[a]++,d[b]++;
}
for(int i=1;i<=n;i++){
res+=max( 0, d[i]-k);//只要超过就删除
}
dfs(1,-1);
cout<<res<<endl;
}
return 0;
}
旅游规划
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 200010, M = N * 2;
int n;
int h[N], e[M], ne[M], idx;
int d1[N], d2[N], p1[N], up[N];
int maxd;
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs_d(int u, int father)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j != father)
{
dfs_d(j, u);
int distance = d1[j] + 1;
if (distance > d1[u])
{
d2[u] = d1[u], d1[u] = distance;
p1[u] = j;//p1记录每个节点的最长路径的 子节点
}
else if (distance > d2[u]) d2[u] = distance;
}
}
maxd = max(maxd, d1[u] + d2[u]);
}
void dfs_u(int u, int father)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j != father)
{
//up[j]的值 肯能有3种情况 up[u]+1,d1[u]+1,d2[u]+1 的最大值
up[j] = up[u] + 1;//记录要走的这个j节点的up[j]为他的父节点的最长路径
if (p1[u] == j) up[j] = max(up[j], d2[u] + 1);//发现这个点要走的子节点 恰好是最长的节点
else up[j] = max(up[j], d1[u] + 1);
dfs_u(j, u);
}
}
}
int main()
{
scanf("%d", &n);
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; i ++ )
{
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
dfs_d(0, -1);
dfs_u(0, -1);
for (int i = 0; i < n; i ++ )
{
int d[3] = {d1[i], d2[i], up[i]};
sort(d, d + 3);
if (d[1] + d[2] == maxd) printf("%d\n", i);
}
return 0;
}
树的中心https://www.acwing.com/problem/content/1075/
找到一个点到树中其他节点的最远距离最近
需要向下d1[] d2[] 以及向上找 up[]
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 10010, M = N * 2, INF = 0x3f3f3f3f;
int n;
int h[N], e[M], w[M], ne[M], idx;
int d1[N], d2[N], p1[N], up[N];
bool is_leaf[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
int dfs_d(int u, int father)
{
d1[u] = d2[u] = -INF;//有可能有0的边?
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (j == father) continue;
int d = dfs_d(j, u) + w[i];//模板 d为u到j点的距离
if (d >= d1[u])
{
d2[u] = d1[u], d1[u] = d;
p1[u] = j;
}
else if (d > d2[u]) d2[u] = d;
}
if (d1[u] == -INF)//如果d1 没有被更新过 说明该点是叶子结点
{
d1[u] = d2[u] = 0;
is_leaf[u] = true;
}
return d1[u];
}
void dfs_u(int u, int father)
{
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (j == father) continue;//从父看每一个子结点
if (p1[u] == j) up[j] = max(up[u], d2[u]) + w[i];//父节点最长的路径上的点就是这个子节点 设置up[j]为max
else up[j] = max(up[u], d1[u]) + w[i];
dfs_u(j, u);
}
}
int main()
{
cin >> n;
memset(h, -1, sizeof h);
for (int i = 0; i < n - 1; i ++ )
{
int a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
dfs_d(1, -1);
dfs_u(1, -1);
int res = d1[1];
for (int i = 2; i <= n; i ++ )
if (is_leaf[i]) res = min(res, up[i]);//如果是叶子结点 那么结果是父亲
else res = min(res, max(d1[i], up[i]));//如果不是叶子结点 结果是上部的
printf("%d\n", res);
return 0;
}
有依赖的背包问题
i j i为某个点为根的节点 j表示以某个根节点为体积的
分组背包:
for(每一组i)
for( 每个体积j)
for(每组某个物品k){
if(v[i][k]<=j)
}
}
这里每一组代表的是子树 k代表的是分配的体积
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N], w[N];
int h[N], e[N], ne[N], idx;
int f[N][N];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u)
{
for (int i = h[u]; ~i; i = ne[i]) // 循环物品组 即每个子树
{
int son = e[i];
dfs(e[i]);//一直往下搜直到边
// 分组背包
for (int j = m - v[u]; j >= 0; j -- ) // 循环体积 这个体积最大从m-v[u]开始 因为需要剩余出给v[u]的体积 下面01背包优化了
for (int k = 0; k <= j; k ++ ) // 循环决策选择第几个东西 实际上是枚举的体积 表示给子树剩下多少体积
f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);//等于以son为根节点第k个子树 加上 以u为根节点缺少了k体积的那部分的和为子树的和 f[son][k] k表示分给子节点子树的体积
}
// 上面已经找到最大值了 现在将物品u的价值加进去 这里也可以在上面放
for (int i = m; i >= v[u]; i -- ) f[u][i] = f[u][i - v[u]] + w[u];
for (int i = 0; i < v[u]; i ++ ) f[u][i] = 0;//上面的那个for循环利用了这个值 所以需要重新设置为0
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
int root;
for (int i = 1; i <= n; i ++ )
{
int p;
cin >> v[i] >> w[i] >> p;
if (p == -1) root = i;
else add(p, i);
}
dfs(root);//根节点往下走
cout << f[root][m] << endl;
return 0;
}
-----
#include<iostream>
#include<vector>
using namespace std;
int f[110][110];//f[x][v]表达选择以x为子树的物品,在容量不超过v时所获得的最大价值
vector<int> g[110];
int v[110],w[110];
int n,m,root;
int dfs(int x)
{
for(int i=v[x];i<=m;i++) f[x][i]=w[x];//点x必须选,所以初始化f[x][v[x] ~ m]= w[x]
for(int i=0;i<g[x].size();i++)
{
int y=g[x][i];
dfs(y);
for(int j=m;j>=v[x];j--)//j的范围为v[x]~m, 小于v[x]无法选择以x为子树的物品 如果小于v[x]说明连u都选不了更别说k了
{
for(int k=0;k<=j-v[x];k++)//分给子树son的空间不能大于j-v[x],不然都无法选根物品x
{
f[x][j]=max(f[x][j],f[x][j-k]+f[y][k]);//表示j-k给x的体积为在已经给够x的基础上少给了一点 j-k一定大于v[i] (j-k)+k==j表示已经分给他的体积
//k表示需要给到son的东西
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
int fa;
cin>>v[i]>>w[i]>>fa;
if(fa==-1)
root=i;
else
g[fa].push_back(i);
}
dfs(root);
cout<<f[root][m];
return 0;
}
二叉苹果树https://www.acwing.com/problem/content/1076/
边作为体积 边的体积在计算的时候需要算作u的值 而不是son 的值
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110, M = N << 1;
int n, m;
int h[N], e[M], w[M], ne[M], idx;
int f[N][N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs(int u, int father)
{
for (int i = h[u]; ~i; i = ne[i])
{
int ver = e[i];
if (ver == father) continue;
dfs(ver, u);
for (int j = m; j >= 1; j -- )
for (int k = 0; k <= j - 1; k ++ ) //枚举体积预留一条连向父节点的边
f[u][j] = max(f[u][j], f[u][j - k - 1] + f[ver][k] + w[i]);
}
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d%d", &n, &m);
for (int i = 1; i < n; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dfs(1, -1);
printf("%d\n", f[1][m]);
return 0;
}