正题
题目链接:https://www.luogu.com.cn/problem/P4322
题目大意
nnn个点的一棵树,每个节点有一个(si,pi)(s_i,p_i)(si,pi),选择一个点必须选择它的父节点,求选择KKK个点使得∑pxi∑sxi\frac{\sum p_{x_i}}{\sum s_{x_i}}∑sxi∑pxi最大。
解题思路
我只会做裸题了/kk
直接上0/10/10/1分数规划然后树形背包。
树形背包要优化,有两种优化方法
- 因为每个节点的容量是111,所以j,kj,kj,k维枚举的时候只需要枚举到sizx,sizysiz_x,siz_ysizx,sizy即可,这样时间复杂度是O(n2)O(n^2)O(n2)的,但是适用性并不强,实际上树形背包常用的优化还是第二种。
- 儿子兄弟表示法,左儿子是它的第一个子节点,右儿子是它的下一个兄弟,那这样就变成了选择左儿子之前必须选择这个点,dpdpdp即可。当然可以换一种理解方法,那就是两个方程frfnx,j=max{frfnx+sizx,j,fi+1,j−vi+wi}f_{rfn_x,j}=max\{f_{rfn_x+siz_x,j},f_{i+1,j-v_i}+w_i\}frfnx,j=max{frfnx+sizx,j,fi+1,j−vi+wi}(rfnxrfn_xrfnx指点xxx的dfsdfsdfs序列)表示是否xxx跳过这个子树。时间复杂度也是O(n2)O(n^2)O(n2)
codecodecode
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=2600;
const double eps=1e-5;
struct node{int to,next;
}a[N];
int n,k,tot,cnt,ls[N],dfn[N],siz[N];
double s[N],p[N],f[N][N];
void addl(int x,int y){a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;return;
}
void dfs(int x){siz[x]=1;dfn[cnt++]=x;for(int i=ls[x];i;i=a[i].next){int y=a[i].to;dfs(y);siz[x]+=siz[y];}return;
}
bool check(double mid){for(int i=n;i>=0;i--){int x=dfn[i];for(int j=0;j<=k;j++)f[i][j]=f[i+siz[x]][j];for(int j=1;j<=k;j++)f[i][j]=max(f[i][j],f[i+1][j-1]+p[x]-s[x]*mid);}return f[0][k]>=0;
}
int main()
{scanf("%d%d",&k,&n);k++;for(int i=1;i<=n;i++){scanf("%lf%lf",&s[i],&p[i]);int x;scanf("%d",&x);addl(x,i);}for(int i=1;i<=k;i++)f[n+1][i]=-1e18;double l=0,r=1e4;dfs(0);while(r-l>eps){double mid=(l+r)/2.0;if(check(mid))l=mid;else r=mid;}printf("%.3lf",(l+r)/2.0);
}