CF1249F Maximum Weight Subset(树形DP or 贪心)

You are given a tree, which consists of n vertices. Recall that a tree is a connected undirected graph without cycles.
Example of a tree.
Vertices are numbered from 1 to n. All vertices have weights, the weight of the vertex v is av.
Recall that the distance between two vertices in the tree is the number of edges on a simple path between them.
Your task is to find the subset of vertices with the maximum total weight (the weight of the subset is the sum of weights of all vertices in it) such that there is no pair of vertices with the distance k or less between them in this subset.

Input
The first line of the input contains two integers n and k (1≤n,k≤200) — the number of vertices in the tree and the distance restriction, respectively.
The second line of the input contains n integers a1,a2,…,an (1≤ai≤105), where ai is the weight of the vertex i.
The next n−1 lines contain edges of the tree. Edge i is denoted by two integers ui and vi — the labels of vertices it connects (1≤ui,vi≤n, ui≠vi).
It is guaranteed that the given edges form a tree.

Output
Print one integer — the maximum total weight of the subset in which all pairs of vertices have distance more than k.

Examples
input
5 1
1 2 3 4 5
1 2
2 3
3 4
3 5
output
11
input
7 2
2 1 2 1 2 1 1
6 4
1 5
3 1
2 3
7 5
7 4
output
4

题目大意:
给定一个树,要求选定一些节点,节点两两之间距离大于K,每个节点有权值,求总权值最大的点集。

树形DP

dp[i][j]表示以节点i为根的子树向下j-1深度的节点都不取,其他节点任意时,子树的最优解(最大权值)。
首先用dp[i][j]记录以i为节点的子树取的节点最浅深度为j时的最大值,然后求dp[i]的后缀最大值就得到要求的dp值。
分两种情况:
若j=0
d p [ i ] [ 0 ] = w e i g h t [ i ] + ∑ d p [ i s o n ] [ k ] dp[i][0]=weight[i]+\sum_{} dp[ison][k] dp[i][0]=weight[i]+dp[ison][k]
ison表示i节点的儿子,上述方程表示取i节点,距离i距离<=k的节点不取,其他节点任意时的最优解。
若j不等于0
d p [ i ] [ j ] = dp[i][j]= dp[i][j]=
m a x ( d p [ i s o n ] [ j − 1 ] + ∑ d p [ o t h e r i s o n ] [ m a x ( j − 1 , k − j ) ] ) max(dp[ison][j-1]+\sum_{}dp[otherison][max(j-1,k-j)]) max(dp[ison][j1]+dp[otherison][max(j1,kj)])
otherison表示i的儿子中除去ison的儿子,max(j-1,k-j)保证最浅深度一定为j。
最后不要忘记求后缀最大值

for(int j=k;j>=0;j--)dp[i][j]=max(dp[i][j],dp[i][j+1]);

这个dp过程可用dfs实现。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<algorithm>
#define lol long long
using namespace std;
lol read()
{
	lol x=0,y=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')y=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*y;
}
int n,k;
bool cont[205][205],b[205];
int weight[205],dp[205][205];
void dfs(int now)
{
	b[now]=true;
	int son[205];
	son[0]=0;
	for(int i=1;i<=n;i++)
	{
		if(cont[now][i]&&(!b[i]))dfs(i),son[++son[0]]=i;
	}
	//统计当前节点的儿子并搜索
	b[now]=false;
	dp[now][0]=weight[now];
	for(int i=1;i<=son[0];i++)dp[now][0]+=dp[son[i]][k];
	//j=0时的操作
	for(int i=1;i<=k;i++)
	{
		int tot=0;
		for(int j=1;j<=son[0];j++)
		{
			tot+=dp[son[j]][max(i-1,k-i)];
		}
		for(int j=1;j<=son[0];j++)
		{
			dp[now][i]=max(dp[now][i],dp[son[j]][i-1]+tot-dp[son[j]][max(i-1,k-i)]);
		}
	}
	//j不等于0时的操作
	for(int i=k;i>=0;i--)dp[now][i]=max(dp[now][i],dp[now][i+1]);
	//后缀最大值
}
int main()
{
	n=read(),k=read();
	for(int i=1;i<=n;i++)weight[i]=read();
	for(int i=1;i<n;i++)
	{
		int a=read(),b=read();
		cont[a][b]=cont[b][a]=true;
	
	dfs(1);
	printf("%d",dp[1][0]);
	return 0;
}

贪心做法

取了一个节点,用ans+=它的权值,那么我们把离它距离<=k的点的权值减去当前节点的权值(包括它自己),然后再找权值大于0的节点重复操作,这样操作后,我们就不用顾虑节点之间是否冲突,比如说取a节点后,在它k距离范围内有取了一个b点,但由于之前的操作,结果等价ans+=max(weight[a],weight[b])。
但这个做法要注意,刚才举的例子中,取了b节点之后,把b节点距离k以内的节点减去现在的b的权值,其实还要把在a领域而不在b领域的节点再加回a的权值,但这样做太麻烦了,所以我们从树的深度最深的节点开始操作,深度大的节点一定要在深度浅的节点前面操作,这样就规避了那个麻烦的操作,因为那些节点无论加不加回a的权值都为负,对后面操作没有影响,算法完成后所有点的权值一定为负。有点绕,可以手动模拟一下。
举个例子,我给一颗树,n个节点,权值全为1,2到n号节点全是1的儿子,以1为根,k=1,如果从1开始操作,只操作一次就让所有节点权值都<=0,ans=1,明显不对。先处理2到n节点就能得出正确答案。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<queue>
#include<vector>
#include<cstring>
#include<cmath>
#include<algorithm>
#define lol long long
using namespace std;
lol read()
{
	lol x=0,y=1;char c=getchar();
	while(c>'9'||c<'0'){if(c=='-')y=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	return x*y;
}
int n,k;
bool cont[205][205],b[205];
int weight[205],ans,dis[205][205],deep[205],fs[205];
queue<int>q;
bool cmp(int a,int b){return deep[a]>deep[b];}
int main()
{
	n=read(),k=read();
	for(int i=1;i<=n;i++)weight[i]=read();
	for(int i=1;i<n;i++)
	{
		int a=read(),b=read();
		cont[a][b]=cont[b][a]=true;
	}
	for(int i=1;i<=n;i++)
	{
		q.push(i);
		b[i]=true;
		while(!q.empty())
		{
			int now=q.front();
			q.pop();
			for(int j=1;j<=n;j++)
				if(cont[now][j]&&(!b[j]))dis[i][j]=dis[i][now]+1,q.push(j),b[j]=true;
		}
		memset(b,0,sizeof(b));
	}
	//用n次bfs预处理出两个节点间的距离
	for(int i=1;i<=n;i++)deep[i]=dis[1][i],fs[i]=i;
	sort(fs+1,fs+n+1,cmp);
	//按深度排序
	for(int i=1;i<=n;i++)
	{
		int now=fs[i];
		if(weight[now]>0)
		{
			ans+=weight[now];
			for(int i=1;i<=n;i++)
				if(dis[now][i]<=k&&now!=i)weight[i]-=weight[now];
			weight[now]=0;
		}
	}
	//如上操作
	printf("%d",ans);
	return 0;
}

你可能感兴趣的:(DP,CF1248F,树形DP/贪心)