树形DP杂练

洛谷 P1352 没有上司的舞会

emmm,树形DP模板题属于是

状态:

fi,1/0 表示以 i 为根的子树中,i 选与不选,可以获得的最大欢乐度。

转移:

fu,0+=max(fv,0,fv,1)

fu,1+=fv,0

嗯,顺便放一个树形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练手题,其实就是把背包放到了树上。

原本采用 fx,i,j 表示以 x 为根节点的子树,对于前 i 个节点选 j 门课的方案数。

既然他是背包,那就采用背包传统降维策略,将状态改为 fi,j 表示对于 i 的子树,最多选 j 门课的方案数。

其次就是DP的顺序,如果第一重循环顺序枚举的话,到后面的时候,fu,jk 就会覆盖了 fu,j,就导致重复,所以我们倒序枚举。

同时,还需要注意的是在遍历到 u 这个节点是,u 实际上是不会被选到的。

#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]保安站岗

算是这个阶段稍微难一点的一道题。

fi,0/1/3 表示对于节点 i 看守它的是它自己/它的子节点/它的父节点。

转移根据状态模拟即可。

值得注意的是 +== 的区别。
主要看递归的顺序。

#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.旅游规划

虽然这是一棵树,但是它的边是没有方向的,因此吗,每个点都可以作为根,可是如果直接这样求直径的话,肯定会超时,因此我们换一种方法。

我们任取一个节点作为根,因为是直径,它有且仅有一个深度最小的节点,当然,我们不用考虑这个节点是不是根节点。

我们可以通过两次 dfs 求解,第一次 dfs 从叶子节点搜向根节点,求出每个节点向下的两个最长路径,将两个路径的长度加和后打擂台取 max 就可以得到直径的长度。

处理完直径后,剩下的就是直径中的点。

我们可以按照前面的方法,对于每一个节点,判断它的最大的两个路径之和是否等于直径的长度,由于第一次 dfs 是处理的都是向下的路径,所以我们第二遍 dfs 要从根节点搜向叶子结点,处理每个节点的向上的最大路径。

在下面是核心代码:

其中 d1,d2 分别为下边的两条最大路径,res 为下边路径的过程,up 为上边的最大路径

(注:因为有可能有多个子节点,所以求两个下边的最大路径。而每个节点只有一个父节点,所以只求一个上边的最大路径)

找下面的路径(先处理子节点):

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

首先是 n=k 的情况

未完待续……

posted @   iFear  阅读(47)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
Live2D
欢迎阅读『树形DP杂练』
点击右上角即可分享
微信分享提示