P13680 [IAMOI R2] 未送出的花 题解

在博客食用以获得最佳体验(逃)

分享一个与官方题解显著不同但疑似本质相同的做法

我们发现对于每一个\(ans_k=t\),其实等价于选择\(n-t\)个点使得至少有\(k\)个点他们从根节点到该节点的路径上有至少半数点都被选择

这样的话我们可以发现如果一个点被选择,但是他的父亲没有被选择,那么选择他的父亲替换掉他是肯定不劣的

我们还可以发现每一个点都有一个恰好的点去激活他,每个点确定激活的节点数量记为\(val_i\)

所以对于一个树,他的所有节点全部被激活的时候,他最少的被选择的节点肯定组成了一个确定的连通块

对于这个连通块,我们再去以他的子树大小\(siz_i\)为物品价格,以他的子树权值大小\(w_u=\Sigma_{v\in son_u} w_v~~+val_u\)为物品价值,跑一个树形背包去统计答案即可

对于答案的统计可以轻易推出\(ans_{n-dp_{1,i}}=siz_i-i\)

代码:

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
using pii=pair<int,int>;
const int N=1e4+5,MOD=998244353;

int n;
vector<int> G[N];
int dep[N],cnt;
int fa[N];
int val[N],siz[N],res[N];
int dp[N][N];

void dfs(int u)
{
	dep[u]=dep[fa[u]]+1;
	for(int v:G[u])
	{
		if(v==fa[u]) continue;
		fa[v]=u,dfs(v);
	}
}

void dfs2(int u)
{
	siz[u]=0,cnt++,dp[u][0]=0;
	for(int v:G[u])
	{
		if(v==fa[u]||val[v]==0) continue;
		dfs2(v);
		for(int k2=siz[u];k2>=0;k2--)
			for(int k1=1;k1<=siz[v]&&k1+k2<=n;k1++)
				dp[u][k2+k1]=min(dp[u][k2]+dp[v][k1],dp[u][k2+k1]);
		siz[u]+=siz[v],val[u]+=val[v];
	}
	siz[u]++;
	dp[u][siz[u]]=val[u];
}

void solve()
{
	cnt=0;
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=n;j++)
			dp[i][j]=1e9;
	for(int i=1;i<=n;i++)
		G[i].clear(),val[i]=fa[i]=dep[i]=res[i]=0;
	for(int i=1;i<n;i++)
	{
		int u,v;
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	fa[1]=1,dfs(1);
	for(int i=1;i<=n;i++)
	{
		int now=i;
		while((dep[now]-1)*2>=dep[i]) 
			now=fa[now];
		val[now]++;
	}
	dfs2(1);
	for(int i=0;i<siz[1];i++)
		if(dp[1][i]<=n) res[n-dp[1][i]]=cnt-i;
	for(int i=n;i;i--)
	{
		if(res[i]) res[i]=n-res[i]+1;
		else res[i]=res[i+1];
	}
	for(int i=1;i<=n;i++) cout<<res[i]<<" ";
	cout<<'\n';
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	int _;
	cin>>_;
	while(_--)solve(); 
	return 0;
}
posted @ 2025-08-12 23:08  Sgt_Dante  阅读(6)  评论(0)    收藏  举报