洛谷P13680 [IAMOI R2] 未送出的花
题目描述
树上开了 \(n\) 朵花,花之间由 \(n-1\) 根树枝连接。第 \(1\) 朵花是树上最高的花,每朵花都可以通过树枝与最高的花直接或间接地连接。
每朵花都有盛开度和美丽值。你可以给每朵花确定一个盛开度,使所有花的盛开度构成一个 \(1\) 到 \(n\) 的排列。一朵花的美丽值为其到最高的花的简单路径上所有花的盛开度的中位数,其中中位数定义为将一个包含 \(m\) 个数的序列从大到小排序后的第 \(\lceil\frac{m}{2}\rceil\) 个数。
邦邦想折下 \(k\) 朵花送出,使送出的 \(k\) 朵花中美丽值最小的花美丽值尽可能大。你需要对于 \(k=1,2,3,\dots,n\) 分别求出这朵花的美丽度是多少,\(k\) 不同时花朵的盛开度可以不同。
输入格式
本题有多组测试数据。
输入的第一行包含一个整数 \(T\),表示测试数据的组数。
接下来包含 \(T\) 组数据,每组数据的格式如下:
-
第一行包含一个正整数 \(n\),表示花朵的数量。
-
接下来 \(n-1\) 行,每行包含两个正整数 \(u,v\),表示第 \(u\) 朵花和第 \(v\) 朵花之间有一根树枝连接。
输出格式
对于每组测试数据输出一行,包含 \(n\) 个整数,其中第 \(i\) 个整数表示 \(k=i\) 时的答案。
输入输出样例 #1
输入 #1
2
8
5 2
3 6
1 3
4 2
2 1
5 7
5 8
12
1 3
9 4
5 3
7 6
8 12
4 1
2 1
10 8
10 11
6 4
8 5
输出 #1
8 8 8 7 7 7 7 6
12 12 12 12 11 11 11 10 10 9 9 9
说明/提示
【样例解释】
对于第一组测试数据,每朵花的盛开度为 \(8,7,6,5,4,3,2,1\) 时,每朵花的美丽值分别为 \(8,8,8,7,7,6,7,7\),此时对于所有 \(k\) 均满足题目的要求。
【数据范围】
本题采用捆绑测试。
记 \(\sum n\) 表示单个测试点中 \(n\) 的和。
| \(\text{Subtask}\) | \(\sum n\le\) | 特殊性质 | 分值 |
|---|---|---|---|
| \(1\) | \(10\) | 无 | \(10\) |
| \(2\) | \(20\) | 无 | \(20\) |
| \(3\) | \(400\) | 无 | \(30\) |
| \(4\) | \(10^4\) | 有 | \(10\) |
| \(5\) | \(10^4\) | 无 | \(30\) |
- 特殊性质:令 \(deg_i\) 表示与第 \(i\) 朵花直接相连的花的数量,\(\forall i\in[2,n]\),\(deg_i\le 2\)。
对于所有的测试数据,保证:\(1\le T\le 100\),\(1\le n,\sum n\le 10^4\),\(1\le u,v\le n\)。
解题报告 ( 树上背包 )
参考的题解:
思路分析
结论:整棵树盛开度的最优形态一定是一个大根堆。
证明:考虑使用调整法,假设现在有一个儿子的盛开度大于父亲的盛开度,现在尝试交换儿子和父亲的盛开度:
- 对于儿子的子树中的节点,根到它们的路径的盛开度序列不变,美丽值不发生变化。
- 对于父亲子树中且非儿子子树中的节点,根到它们的路径只经过父亲而不经过那个儿子,所以盛开度序列中有且仅有一个数会变大,美丽值不降。
- 对于非父亲子树中的节点,没有变化。
所以此时交换儿子和父亲一定不劣,得证。
因此,题目中关于中位数的条件是假的,一个点 u 的美丽值就是根到它的路径上第 ⌈$ \frac {dep_u}{2}\(⌉ 个点的盛开度,\)dep_u$ 表示深度。
那么对于每个点,我们可以求出它被作为美丽值的次数,记为 \(cnt_i\) ,通过一次 $ DFS $ 求出所有的 \(cnt\)
先考虑简化的问题:如果给定了这棵树每个点美丽值和 $ k $ ,如何求出答案?
显然是应该将所有的美丽值排序,按美丽值从小到大累计 $ cnt $ ,直到 总 $ cnt \geq k $ ,最后一个 $ cnt $ 就是答案
结合整棵树盛开度的最优形态一定是一个大根堆这个结论,把盛开度从大到小排序后,一段前缀盛开度一定是一个 包含节点 \(1\) 的连通块
那么本题的问题可以转化为:对于所有的 $ k∈[1,n]$,求一个包含 节点 \(1\) 的联通块,满足这个联通块的 $ \sum cnt_i \ge k $,
同时联通块的点数最小。
可以树形 $ DP $
设 $ dp[u][i] $ 表示 在节点 $ u $ 的子树中,满足 $ \sum cnt = i $ 且 包含节点 $ u $ 的最小联通块大小
由于要构成一个联通块,那么如果不选点 \(u\) 那么 \(u\) 的子树中的点都不能选,一个有限制的背包问题。
那么状态转移方程为:
$ dp[u][i]=min( dp[u][i],dp[u][j]+dp[v][i-j] ) $
$ v $ 表示 所有与 节点 $ u $ 直接相连的子节点,$ j \in [0,i] $
初始化 $ dp[i][ cnt_i ]=1 $ ,从而保证转移时能处理节点 $ u $
答案 $ ans[k]=n-min(dp[1][i])+1$ ,其中 $ i \in [k,n] $ ,这是由于需要$ \sum cnt_i \ge k $
代码
#include<bits/stdc++.h>
using namespace std;
const int INF=0x3f3f3f3f;
const int N=10001;
inline int read()
{
int f=1,x=0; char ch=getchar();
while(!isdigit(ch)) { if(ch=='-') f=-1; ch=getchar(); }
while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar(); }
return f*x;
}
vector<int> e[N];
int n,ans[N];
inline void DoubleEdge(int u,int v)
{
e[u].push_back(v);
e[v].push_back(u);
}
int cnt[N];
int st[N],top;
void dfs(int u,int fa)
{
st[++top]=u;
cnt[ st[(top+1)>>1] ]++;
for(auto v:e[u])
{
if(v==fa) continue;
dfs(v,u);
}
st[top--]=0;
}
int sum[N];
int dp[N][N],tmp[N];
void calc(int u,int fa)
{
sum[u]=cnt[u];
dp[u][ cnt[u] ]=1;
for(auto v:e[u])
{
if(v==fa) continue;
calc(v,u);
memcpy(tmp,dp[u],sizeof(dp[u]));
for(int i=0;i<=sum[u];i++)
for(int j=0;j<=sum[v];j++)
dp[u][i+j]=min(dp[u][i+j],tmp[i]+dp[v][j]);
sum[u]+=sum[v];
}
}
signed main()
{
freopen("P13680.in","r",stdin);
freopen("P13680.out","w",stdout);
int Q=read();
while(Q--)
{
n=read();
for(int i=1;i<=n;i++) e[i].clear();
for(int i=1;i<=n;i++) sum[i]=cnt[i]=0;
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
dp[i][j]=INF;
// 这里用 memset 会超时
for(int i=1;i<n;i++)
{
int u=read(),v=read();
DoubleEdge(u,v);
}
dfs(1,0); calc(1,0);
int tot=INF;
for(int i=n;i>0;i--)
{
tot=min(tot,dp[1][i]);
ans[i]=n-tot+1;
}
for(int i=1;i<=n;i++)
printf("%d ",ans[i]);
putchar('\n');
}
return 0;
}

浙公网安备 33010602011771号