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;
}

浙公网安备 33010602011771号