CF1646D题解

原题

CF1646D Weight the Tree


思路概述

题意分析

给定一个 \(n\) 个点的无根树,定义一种点为“好节点”。一个点为好节点当且仅当其权值等于与其相连节点的权值和。要求求出该树中最多的好节点和在此前提下所有节点最小的权值和,并输出方案。

思路简述

由于每个点的权值都会影响与其相连节点的状态(即是否为好节点),所以考虑树形DP。

本题中,每个点的状态都有是好节点和不是好节点两种状态,因此可以得出本题的状态定义:一维用于表示点的下标,另一维用于表示当前的点是否为好节点。

得到最优解后,再按相同的转移方程搜索一次,求出每一个节点的权值。


算法实现

关于动态转移方程

经过简单分析不难得知:节点权值之间存在一定决定关系,即好节点的权值取决于其周围的非好节点的权值。若要求所有节点权值和最小,则非好节点的权值就应全为 \(1\)

根据上文分析可知,不存在两个相邻的好节点。换而言之,若一个节点是好节点,其所有子节点必须是非好节点,其父节点也应是非好节点。所以好节点的状态只能从其子节点非好节点的状态开始转移。

而对于一个非好节点,其子节点与父节点则没有限制,取最优的情况进行转移即可。

由上述就可以得到本题的动态转移方程:

\[\text{令}f_{i,j}(1≤i≤n,0≤j≤1)\text{表示}i\text{作为好节点或非好节点时得到其本身与子树上的最大好节点数量} \]

\[e_{i,j}(1≤i≤n,0≤j≤1)\text{表示}i\text{作为好节点或非好节点时得到其本身与子树上满足好节点数量最大的前提下的最小权值和} \]

\[better(x)\text{表示}x\text{两状态中更优的状态,即}z∈\{0,1\}\text{使得}f_{x,z}=max\{f_{x_i}\},e_{x,z}=min\{e_{x,i}\} \]

\[\text{令}son_i(1≤i≤n)\text{表示}i\text{所有子节点的集合} \]

\[\text{令}cnt(i)(1≤i≤n)\text{表示}i\text{出边数量} \]

\[\text{则有}f_{i,j}=\begin{cases} \ \sum_{k∈son_i}f_{k,0},j=1\\\sum_{k∈son_i}f_{k,better(k)},j=0 \end{cases},e_{i,j}=\begin{cases} \ cnt(i)+\sum_{k∈son_i}e_{k,0},j=1\\1+\sum_{k∈son_i}e_{k,better(k)},j=0 \end{cases} \]

关于搜索

由于转移过程中不能记录路径,所以得出最优解后就必须再搜索一次求出每个点的权值。搜索策略与转移方程一致,如下:

	for(RI i=fir[x];i;i=s[i].nex)
		if(s[i].u!=pre)
		{
			if(mrk || f[s[i].u][0]>f[s[i].u][1]) dfs_edt(s[i].u,x,0);
			else dfs_edt(s[i].u,x,1);
		}

部分注意事项

由于 \(n=2\) 时,通过 \(val_1=val_2=1\) 可以构造出好节点最多且权值和最小的情况,需要在读入时特判。

由于每个点作为好节点时的权值需要初始化为出边数量,所以读入边的时候就可以记录每个点入度。


AC code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<set>
#include<ctime>
#define RI register int
using namespace std;
const int maxn=2e5+10;
typedef struct
{
	int u,nex;
}side;
typedef struct state
{
	int num,val;
	inline bool operator >(const state &x)const{return (num==x.num)?(val<x.val):(num>x.num);}/*重载优先级判定*/
}node;
side s[maxn<<1];
state f[maxn][2];
int n,cnt;
int fir[maxn],sp[maxn],rec[maxn];
inline void add(int x,int y);
inline void dfs(int x,int pre);
inline void dfs_edt(int x,int pre,bool mrk);
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin >> n;
	/*存在一种特殊情况 即n=2时 两个节点都可以是好节点*/ 
	if(n==2) printf("2 2\n1 1\n");
	else
	{
		for(RI i=1,x,y;i<n;++i)
		{
			cin >> x >> y;
			add(x,y);++sp[x];++sp[y];
		}
		dfs(1,1);
		/*对根节点1作为好节点和非好节点的情况进行判断*/
		if(f[1][0]>f[1][1])
		{
			printf("%d %d\n",f[1][0].num,f[1][0].val);
			dfs_edt(1,1,0);
		}
		else
		{
			printf("%d %d\n",f[1][1].num,f[1][1].val);
			dfs_edt(1,1,1);
		}
		for(RI i=1;i<=n;++i) printf("%d ",rec[i]);
	}
	
	return 0;
}
inline void add(int x,int y)
{
	s[++cnt]=(side){y,fir[x]};fir[x]=cnt;s[++cnt]=(side){x,fir[y]};fir[y]=cnt;
	return;
}
inline void dfs(int x,int pre)
{
	f[x][0]=(state){0,1};f[x][1]=(state){1,sp[x]};
	for(RI i=fir[x];i;i=s[i].nex)
		if(s[i].u!=pre)
		{
			dfs(s[i].u,x);
			if(f[s[i].u][0]>f[s[i].u][1]) f[x][0]=(node){f[x][0].num+f[s[i].u][0].num,f[x][0].val+f[s[i].u][0].val};/*下个好节点作为好节点时更优*/ 
			else f[x][0]=(node){f[x][0].num+f[s[i].u][1].num,f[x][0].val+f[s[i].u][1].val};
			f[x][1]=(node){f[x][1].num+f[s[i].u][0].num,f[x][1].val+f[s[i].u][0].val};
		}
	return;	
}
inline void dfs_edt(int x,int pre,bool mrk)
{
	rec[x]=mrk?sp[x]:1;
	for(RI i=fir[x];i;i=s[i].nex)
		if(s[i].u!=pre)
		{
			if(mrk || f[s[i].u][0]>f[s[i].u][1]) dfs_edt(s[i].u,x,0);
			else dfs_edt(s[i].u,x,1);
		}
	return;	
}

posted @ 2022-10-27 20:50  UOB  阅读(30)  评论(0)    收藏  举报