2024年CCPC网络赛C题题解 —— 种树(gym105336C)

      一道不错的树形dp题,想要提升树形dp的糕手们可以做一下,放上题面:

     题意:给你一个有0有1的数,每次可以把一个大小为3(包含3个结点)的结构中,要求至少包含一个1,然后就能把整个块的结点都变成1,问你最少需要这样几次操作能使所有节点都变成1。

    然后赛时其实我没做出来,树形dp这个做法是想到的,但是被J这个线性基卡住了(本人当时没接触过线性基),然后一些题目没有去写。所以我先来讲讲树形dp的解法:

     我们来观察一下,如果这个结构大小要求的是2,那么其实可以直接设置一个有关父节点和子节点的状态转移的dp(这玩意应该比较好想),这道题目如果是大小为3的话,我们可以这样子设置一个dp的状态转移:考虑树形 DP,对于一个子树而言,最多可能有 4 种情况:贡献 1 个给父亲、不需要父亲贡献、父亲贡 献 1 个给该子树、父亲贡献 2 个给该子树。 那么,设 fu,−1/0/1/2 分别表示子树 u 对应的这些情况。转移时考虑枚举 u 所有儿子 v 的情况,注意一 下 u 自己本身是否已经种上树、以及两个 fv1,1、fv2,1 可以合并(即使用一次操作完成)的情况即可。 实现细节可能较多。然后实现的代码是这样的:

#include<bits/stdc++.h>
#define Alex std::ios::sync_with_stdio(false),std::cin.tie(0),std::cout.tie(0);
#define int long long
#define double long double
const int QAQ = 0;
const int mod = 998244353;
const double pi = std::acos(-1.0);
const double eps = 1e-10;

int n,m,p[500005],f[500005][4],g[2][2],h[2][2];
std::vector<int> G[500005];

inline void dfs(int u,int fa)
{
	for(auto v : G[u])
	{
		if(v == fa) continue;
		dfs(v,u);
	}
	for(int i = 0;i <= 1;i++)
	for(int j = 0;j <= 1;j++) g[i][j] = 2e18;
	g[0][p[u]] = 0;
	for(auto v : G[u])
	{
		if(v == fa) continue;
		for(int i = 0;i <= 1;i++)
		for(int j = 0;j <= 1;j++)
		{
			h[i][j] = g[i][j];
			g[i][j] = g[i][j] + f[v][0];
		}
		for(int i = 0;i <= 1;i++)
		{
			g[0][i] = std::min(g[0][i],h[1][i] + f[v][1] + 1);
			g[1][i] = std::min(g[1][i],h[0][i] + f[v][1]);
		}
		for(int i = 0;i <= 1;i++)
		for(int j = 0;j <= 1;j++)
		    g[i][1] = std::min(g[i][1],h[i][j] + f[v][2]);
	}
	f[u][0] = std::min(g[0][1],g[1][0] + 1);
	f[u][1] = g[0][0];
	f[u][2] = std::min(g[0][0],g[1][1]) + 1;
}

signed main()
{
	Alex;
	int _;
	_ = 1;
	std::cin>>_;
	while(_--)
	{
		std::cin>>n>>m;
		for(int i = 0;i <= n + 5;i++)
		for(int j = 0;j <= 2;j++) f[i][j] = 2e18;
		for(int i = 0;i <= 1;i++)
		for(int j = 0;j <= 1;j++)
		{
			g[i][j] = h[i][j] = 2e18;
		}
		for(int i = 0;i <= n + 5;i++) p[i] = 0;
		for(int i = 1;i <= m;i++)
		{
			int x;
			std::cin>>x;
			p[x] = 1;
		}
		for(int i = 0;i <= n + 5;i++) G[i].clear();
		for(int i = 1;i <= n - 1;i++)
		{
			int u,v;
			std::cin>>u>>v;
			G[u].push_back(v);
			G[v].push_back(u);
		}
		dfs(1,0);
		std::cout<<std::min(f[1][0],f[1][2])<<'\n';
	}
	return QAQ;
}


   
   
   
   





第二种做法是贪心:贪心做法 对于一次操作,最多完成两个未完成的节点。 对于一个大小为 siz 的子树,且只有根节点完成了,那么一共需要  siz / 2 次操作。 对子树根节点是否完成进行分类讨论: • 如果根节点已经完成了,那么这个子树对根节点父亲的贡献就有两种情况:用最少次数做完子树 时,是否存在多余的、且对子树根节点的父亲有贡献的操作。 如果根节点没有完成,则直接将 siz 向上传递。直到遇到一个已经完成的祖先,在那个祖先中对 siz 进行统计即可。 总的时间复杂度为 O(n)。这种做法的代码实现是这样的:

#include<bits/stdc++.h>
#define Alex std::ios::sync_with_stdio(false),std::cin.tie(0),std::cout.tie(0);
#define int long long
#define double long double
const int QAQ = 0;
const int mod = 1e9 + 7;
const double pi = std::acos(-1.0);
const double eps = 1e-10;
int f[2000005];
bool p[2000005];
std::vector<int> G[500005];
int n,m,ans = 0;

inline void Fre() { int liujiahui = 142857; }

inline int dfs(int u,int fa)
{
	int res = 0;
	for(auto v : G[u])
	{
		if(v == fa) continue;
		f[v] = dfs(v,u);
	}
	for(auto v : G[u])
	{
		if(v == fa || p[v] == 0) continue;
		int t = f[v];
		if(p[u] == 0 && t != 0) p[u] = 1; 
	}
	for(auto v : G[u])
	{
		if(v == fa || p[v] == 1) continue;
		int t = f[v];
		res += t;
	}
	if(p[u] == 0)
	{
		res++;
		return res;
	}else 
	{
		ans = ans + (res + 1) / 2;
		return res & 1;
	}
}

signed main()
{
	Alex;
	int _;
	_ = 1;
	std::cin>>_;
	while(_--)
	{
		std::cin>>n>>m;
		for(int i = 0;i <= n + 5;i++) p[i] = 0;
		for(int i = 0;i <= n + 5;i++) f[i] = 0;
		for(int i = 0;i <= n + 5;i++) G[i].clear();
		for(int i = 1;i <= m;i++) 
		{
			int x;
			std::cin>>x;
			p[x] = 1;
		}
		for(int i = 1;i <= n - 1;i++)
		{
			int u,v;
			std::cin>>u>>v;
			G[u].push_back(v);
			G[v].push_back(u);
		}
		ans = 0;
		int res = dfs(1,0);
		if(p[1] == 0) ans = ans + (res + 1) / 2;
		std::cout<<ans<<'\n';
	}
	return QAQ;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值