BZOJ 2133 切割(树形DP,树上背包)大概是本题全网第一篇题解 >_<BZOJ 修复工程
Posted 繁凡さん
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BZOJ 2133 切割(树形DP,树上背包)大概是本题全网第一篇题解 >_<BZOJ 修复工程相关的知识,希望对你有一定的参考价值。
整理的算法模板合集: ACM模板
实际上是一个全新的精炼模板整合计划
BZOJ 2133 切割这道题全网搜不到任何一篇题解 >_<
看评测记录也没有几个人AC…
不过其实很简单就是了,可能是大佬们写完之后不屑于写题解吧 o(〃^▽^〃)o
题目链接
https://hydro.ac/d/bzoj/p/2133
是 hydro 的 BZOJ 修复工程 !(我也去领了一点题慢慢修着玩,这题就是我修的嘿嘿嘿)
题目描述
有一棵 n n n 个节点的无根树,节点编号为 1 … n 1\\dots n 1…n。每个节点有一个非负权值。现在需要把这棵树分成若干个联通块(不能有剩下的点),每个联通块的节点个数在 k 1 k_1 k1 和 k 2 k_2 k2 之间,每个联通块的得分就是该联通块中所有节点权值的平均值。总得分就是指对这个树切割后所有联通块的得分的和。你需要求出一种分割的方案使得总得分尽量小。
输入格式
第一行三个整数 n , k 1 , k 2 n,k_1,k_2 n,k1,k2;
第二行 n n n 个整数,第 i i i 个数表示编号为 i i i 的节点的权值,每两个相邻整数之间用一个空格隔开;
接下来 n − 1 n-1 n−1 行,每行两个整数 x , y x,y x,y ,表示编号为 x x x的节点和编号为 y y y的节点之间有一条边。
输出格式
如果存在一个切割方案,那么输出一个实数(四舍五入保留二位小数),表示所有分割方案中最小的总得分。如果不存在切割方案,那么输出所有节点权值和的两倍。如果对于某个测试点你的输出和标准输出完全一样,那么将得到该测试点全部的分,否则该测试点不得分(我写的sp貌似有问题,大家可以使用 double
类型存储数据,输出结果的整数部分保证不超过
9
9
9 位(十进制),使用 double
类型可以保证小数部分至少是
6
6
6位,因而精度应该不存在问题)。
输入样例
3 1 1
1 1 1
1 2
2 3
输出样例
3.00
数据规模与约定
对于 100 % 100\\% 100% 的数据, 1 ≤ k 1 ≤ k 2 ≤ n , 2 ≤ n ≤ 50 1\\le k_1\\le k_2\\le n,2\\le n\\le 50 1≤k1≤k2≤n,2≤n≤50,每个点的权值均不超过 1 0 9 10^9 109 。
Solution
首先随便定一个点 r o o t root root 为树根,将无根树转化为有根树。
数据较大,显然不可能直接爆搜,考虑树形DP。
显然设 f [ i ] f[i] f[i] 表示以 i i i 为根的子树中能获得的最小的总得分,分析题意考虑需要维护哪些信息。
首先题目中给定的计算方式暂时没有什么切入点,先忽略掉,对于每个连通块,块内结点个数是有一个范围限制的,即 k 1 ≤ n u m ≤ k 2 k_1\\le num\\le k_2 k1≤num≤k2,显然我们需要维护这个信息,考虑设 f [ i , j ] f[i,j] f[i,j] 表示以 i i i 为根的子树在切割后,所属的连通块的结点个数为 j j j ,能获得的最小总得分。
发现没法转移…
考虑 n ≤ 50 n\\le 50 n≤50, O ( n 4 ) ∼ O ( n 4 log n ) O(n^4)\\sim O(n^4\\log n) O(n4)∼O(n4logn),甚至 O ( n 5 ) O(n^5) O(n5) 都有可能通过(手动狗头)
考虑再增加一些维护的信息。
树形DP考虑从子树的角度出发分析,题目可以转换为从子树中选择若干个点,加入到根节点的连通块内,即树上背包问题,考虑转移和需要维护的信息。显然我们只能从子树中选取不超过该连通块的总数 j j j 的节点数放入当前连通块内,考虑维护 k k k 表示根节点 i i i 的子树中被选入 i i i 所在的连通块的结点个数,这样我们将问题转化为一个 0/1 背包的模型,直接按照背包 O ( n 2 ) O(n^2) O(n2) 转移即可。
即设 f [ i , j , k ] f[i,j,k] f[i,j,k] 表示以 i i i 为根的 子树 在切割后,结点 i i i 所属的连通块的结点个数为 j j j ,子树中被选入 i i i 所在的连通块的结点个数为 k k k (显然包括),能获得的最小的总得分。
考虑如何维护答案,因为我们并不能确定每个连通块内结点的个数,因此我们直接以 i i i 为根维护答案即可,设 a n s [ i ] ans[i] ans[i] 表示以 i i i 为根的 子树中 的最小总得分,显然我们必须要从子树中选择 j j j 个结点填满背包才算合法,故答案为: a n s [ i ] = min { f [ i ] [ j ] [ j ] ∣ j ∈ [ k 1 , k 2 ] } \\displaystyle ans[i] = \\min\\{f[i][j][j]\\mid j\\in[k_1,k_2]\\} ans[i]=min{f[i][j][j]∣j∈[k1,k2]}
最后的答案显然是 a n s [ r o o t ] ans[root] ans[root]。
最后如果没有合法的方案的话(ans[root] == maxx
),根据题意输出总和的二倍即可。
注意 double
类型的数使用 memset
初始化的时候,
∞
\\infin
∞ 使用 0x7f
,
−
∞
-\\infin
−∞ 使用 0xef
。
Code
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100 + 7, maxm = maxn << 1, INF = 0x3f3f3f3f;
const double DINF = 1e18, eps = 1e-8;
int n, m, s, t;
int k1, k2;
double w[maxn];
double f[maxn][maxn][maxn];
double ans[maxn];
int siz[maxn];
vector <int> e[maxn];
bool vis[maxn];
double sum;
int sgn(double x)
{
if(fabs(x) < eps)
return 0;
if(x < 0)
return -1;
return 1;
}
void dfs(int x, int fa)
{
for (auto y : e[x]) {
if(y == fa) continue;
dfs(y, x);
}
for (int j = k1; j <= k2; ++ j) {
f[x][j][1] = w[x] / j;
for (auto y : e[x]) {
if(y == fa) continue;
for (int k = j; k >= 1; -- k) {
f[x][j][k] = f[x][j][k] + ans[y];
for (int l = 1; l <= k - 1; ++ l)
if(sgn(f[x][j][k] - (f[x][j][k - l] + f[y][j][l])) == 1)
f[x][j][k] = f[x][j][k - l] + f[y][j][l];
}
}
if(sgn(ans[x] - f[x][j][j]) == 1)
ans[x] = f[x][j][j];
}
}
int main()
{
sum = 0;
memset(f, 0x7f, sizeof f);
memset(ans, 0x7f, sizeof ans);
double maxx = ans[1];
scanf("%d%d%d", &n, &k1, &k2);
for (int i = 1; i <= n; ++ i)
scanf("%lf", &w[i]), sum += w[i];
for (int i = 1; i <= n - 1; ++ i) {
int x, y;
scanf("%d%d", &x, &y);
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1, -1);
if(ans[1] == maxx) printf("%.2f", sum * 2.0);
else printf("%.2f", ans[1]);
return 0;
}
以上是关于BZOJ 2133 切割(树形DP,树上背包)大概是本题全网第一篇题解 >_<BZOJ 修复工程的主要内容,如果未能解决你的问题,请参考以下文章
bzoj 2427: [HAOI2010]软件安装tarjan+树形dp