nm树上背包+二分--P4322 [JSOI2016]最佳团体
首先是一些想法:
-
首先看到了平均值,肯定会出现分数,我们想到了分数规划
-
由于我们无法直接从一个状态的最大平均值直接推出另一个状态的平均值,所以我们不能直接dp,想到二分转化问题
solution
假设最后的答案是\(mid\),那么我选中的\(k\)个物品一定满足\(\dfrac{\sum\limits_{i=1}^k p_i}{\sum\limits_{i=1}^k s_i}\geq mid\)
式子变形为:\(\sum\limits_{i=1}^k (p_i-mid\times s_i)\geq 0\),显然这是个存在性命题,只需要最大值满足这个式子就可以了
求上面这个式子的最大值就可以dp求了:把\(p_i-mid\times s_i\)看作是价值,求一个树上背包
状态设计:\(dp_{i,j}\)表示选第\(i\)个物品,在以\(i\)为根的子树里选\(j\)个物品的最大价值,因为不选\(i\),子树也不能选,价值为0无意义
状态转移方程:\(dp_{i,j} = max(dp_{i,j},dp_{to,j-k}+dp_{x,k})\),枚举\(n\)个点,\(m\)个物品,\(m\)个断点,复杂度\(O(n^2m)\),不太行的样子
优化
还有一种\(nm\)树上背包的东西,我们把点重新编号,使根节点为子树中编号最大的点
状态设计:\(dp_{i,j}\)表示在前\(i\)个点里选\(j\)个点的最大价值
状态转移方程:\(dp_{i,j} = max(dp_{i-1,j-1}+val_{pos_i},dp_{i-siz[pos[i]],j})\)
code
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
using namespace std;
int read(){
int x = 1,a = 0;char ch = getchar();
while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
return x*a;
}
const int maxn = 3000,inf = 1e9+7;
int n,k;
struct node{
int to,nxt;
}ed[maxn];
int head[maxn],tot;
void add(int u,int to){
ed[++tot].to = to;
ed[tot].nxt = head[u];
head[u] = tot;
}
int siz[maxn],dfn[maxn],pos[maxn],cnt;
void dfs(int x){
siz[x] = 1;
for (int i = head[x];i;i = ed[i].nxt){
int to = ed[i].to;
dfs(to);
siz[x] += siz[to];
}
dfn[x] = ++cnt,pos[cnt] = x;
}
double val[maxn],f[maxn][maxn];
int p[maxn],s[maxn];
bool check(double x){
for (int i = 0;i <= cnt;i++)
for (int j = 1;j <= k;j++)
f[i][j]=-inf;
for (int i = 1;i <= n;i++)
val[i] = (double)p[i]-x*s[i];
for (int i = 1;i <= cnt;i++){
for (int j = 1;j <= k;j++){
f[i][j] = max(f[i-1][j-1]+val[pos[i]],f[i-siz[pos[i]]][j]);
}
}
return f[cnt][k]>0;
}
int main(){
k = read()+1,n = read();
for (int i = 1;i <= n;i++){
s[i] = read(),p[i] = read();
int x = read();
add(x,i);
}
dfs(0);
double l = 0,r = 100000,eps = 1e-4;
while (r-l >= eps){
double mid=(l+r)/2;
if (check(mid)) l=mid;
else r=mid;
}
printf("%.3lf\n",l);
return 0;
}