首页 > 其他分享 >P3874 砍树 题解

P3874 砍树 题解

时间:2022-12-16 22:35:39浏览次数:44  
标签:二分 int 题解 sum db mid 砍树 P3874 dp

前置

树形 dp,二分。

题意

本质上是一个树上背包,需要选不少于 \(k\) 个物品,每个物品有一个重量 \(w\) 和价值 \(v\),求性价比最大值。

分析

既然是性价比,显然是分数规划。

先介绍一下分数规划是什么:

我们二分这个最大性价比。

假设当前枚举到 \(mid\),则我们将每个点的价值修改为

\[v-mid \times w \]

然后我们正常做树形 dp,然后统计一下是否有价值大于等于 \(0\) 的即可。

那么为什么这样呢?

假设性价比为 \(g\),我们选的是

\[p_1,p_2,...p_s( k\le s \le n) \]

则我们有

\[\frac {\sum_{i=1}^s{v_{p_i}}}{\sum_{i=1}^s{w_{p_i}}}=g \]

进而可以推出

\[\sum_{i=1}^s{w_{p_i} \times g}=\sum_{i=1}^s{v_{p_i}} \]

那么,当我们定义价值 \(val_i=v_i-w_i \times g\) 时,有

\[\sum_{i=1}^s{val_{p_i}}=0\ge0 \]

成立,故以上算法正确。

实现

比较好说,先二分出来 \(g\),然后跑树形背包即可,注意要一边计算大小 \(size_p\) 一边跑背包,不然复杂度 \(O(n^3)\),加上二分可能 TLE。(虽然我没试过)

然后就是注意把精度卡到 \(0.0001\),不然会 WA。

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 110;
typedef double db;

int ver[N * 2], nxt[N * 2], hd[N], idx;

inline void add (int x, int y) {
	ver[++idx] = y;
	nxt[idx] = hd[x];
	hd[x] = idx;
}

int n, w[N], v[N], k, s[N];
bool mk[N];
db dp[N][N], g[N];

void dfs (int u, int fa) {
	dp[u][0] = 0; dp[u][1] = g[u]; s[u] = 1;
	for (int i = hd[u]; i ;i = nxt[i]) {
		int y = ver[i];
		if (y == fa) continue;
		dfs(y, u);
		s[u] += s[y];   										  //注意size和dp要一起算 
		for (int j = min(n, s[u]);j >= 1;j--) {                   //处理背包 
			for (int z = 0;z <= min(j - 1, s[y]);z++) {
				dp[u][j] = max(dp[u][j], dp[u][j - z] + dp[y][z]);
			}
		}
	}
}

bool check (db x) {
	for (int i = 1;i <= n;i++) g[i] = v[i] - x * w[i];                        //处理val 
	for (int i = 1;i <= n;i++) for (int j = 0;j <= n;j++) dp[i][j] = -200000;
	dfs(1, 0); 
	db res = -1;
	for (int i = 1;i <= n;i++) for (int j = k;j <= n;j++) {
		res = max(res, dp[i][j]);
	}
	if (res >= 0) return 1;
	return 0;
}

int main () {
	cin >> n >> k;
	for (int i = 1;i <= n;i++) cin >> v[i];
	for (int i = 1;i <= n;i++) cin >> w[i];
	for (int i = 1;i < n;i++) {
		int x, y;
		cin >> x >> y;
		add(x, y); add(y, x);
	}
	db l = 0, r = 200000;           //二分 
	while (r - l > 0.0001) {        //注意精度 
		db mid = (l + r) / 2;
		if (check(mid)) l = mid;
		else r = mid - 0.0001;
	}
	printf("%.2lf", l);
	return 0;
} 

标签:二分,int,题解,sum,db,mid,砍树,P3874,dp
From: https://www.cnblogs.com/zxh-mc/p/16988412.html

相关文章

  • CF992E Nastya and King-Shamans 题解
    传送门分析由于满足\(a_i\ge0\),所以\(s_i\)单调不减。当我们找到一个\(i\)时,不管\(i\)是否满足,下一个可能的一定大于等于\(a_i+s_{i-1}\)。而且\(a_i+s_{i-1}......
  • P8810 [蓝桥杯 2022 国 C] 数组个数 题解
    思路比较简单的一道题。用的五维dp,看到二维和三维的dp直接膜了orz。正文开始。分析不难看出dp。因为\(b_i\)的值只与\(a_{i-1},a_i,a_{i+1}\)有关,所以我们定......
  • CF939F Cutlet 题解
    题意简述有一个正反面都为\(0\)的卡片,每过\(1\)分朝下那一面的数值就会增加\(1\),你可以在几个区间的时间内翻转卡片,求经过\(2n\)秒后能否让这个卡片的正反面的数都......
  • YACS 11 月甲题解
    https://iai.sh.cn/contest这把还是简单的,难度对标普及组。所有题解均口胡。T1观察&性质你扫左端点,然后维护以当前左端点最长的合法子段,显然右端点单不降,因为当你......
  • 【LeetCode】题解合集(JavaScript版)
    前言:今年断断续续写了些LeetCode题目,一方面是为了一个比较现实的问题——面试,最重要的是要复习之前学习的数据结构与算法。后面有时间还会接续刷题…题解合集:题号题目题解1......
  • NOIP2022 题解
    终于有机会补NOIP的题了T1考虑枚举C与F的纵列考虑预处理出每个点最左边和最下边可以延伸到哪之后枚举列,然后对行做类似于扫描线的操作,统计有多少可行的"第一横行"......
  • NOIP2022 题解
    T2T3......
  • CF1408G 题解
    题意传送门给定\(n\)个点的带权无向完全图,点\(i,j\)之间的权值为\(a_{i,j}\),权值是一个\(1\sim\frac{n(n-1)}{2}\)的排列。计数把原图划分成\(k\)个组的方......
  • TeraTerm 日文乱码问题解决
    windows中文系统上,用TeraTerm连接日文服务器,经常会出现乱码的问题。本篇是本地win11系统,TeraTerm安装时选择英文。Generalsetup依次选择Setup->General.........
  • LeetCode 题解 2487. 从链表中移除节点
    2487.从链表中移除节点-力扣(Leetcode)题解思路一:递归逆序structListNode*removeNodes(structListNode*head){if(head->next==NULL)//遍历到链表末尾......