P4322 [JSOI2016] 最佳团体
看到题解区里都是大片大片的树形 ,这里给出一个不用琢磨复杂度是 还是 的 序做法。
题意
给定一个 个节点的以 为根的树,树上的每个节点都有其价值 和费用 。在树上选择 个节点,在满足任意一个节点如果被选择了,那么其父亲也一定要被选择的条件下,求性价比最高的一个选点方案。
分析
求性价比最高有一个经典套路: 分数规划,这部分其他题解讲的已经很清楚了,这篇题解讲着重讲解 的部分,即如何选出 个点使其点权和最大。
显然,选完 个点后,选择的点就构成了另一棵树,而所有未选择的点构成了一片森林。选择的点构成的树的形态是变化多样的,而未选择的点构成的那些树却遵循原树的性质,正难则反,于是我们可以把问题转化为在符合题意的前提下从 个点中剔除 个节点,使剔除的点权和最小的情况下的点权和。
每次剔除点,我们剔除的连通块必然是一棵树,所以只要知道其根节点即可,考虑用 序来转化问题,在 进入某个节点的时候记录次该节点,退出时也记录一次,将两次的 序分别记为 ,这样一个节点对应的区间就是以该节点为根的子树,同时可以发现,剔除的两棵子树所对应的区间是没有相交部分的,因此树上的问题就可以转化为序列上的选择区间问题。
设 表示以节点 为根的子树的大小, 表示 的节点的编号,若不存在则为 ,再设 表示最后一次剔除的子树根节点为 ,总共剔除了 个节点的最小点权和,那么有:
转移是按照 从小到大转移的, 那部分可以直接用前缀和处理,但是我们直接枚举 的话复杂度仍然高达 ,于是考虑优化,发现 那部分的第二维都一样,于是可以开一个数组 记录 ,但不满足 的前置条件,由于 很小,所以我们干脆再开一维,记 为 ,那么就有:
于是最终时间复杂度降到 ,但本人代码由于常数过大,要开 才能过。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
long long x=0,f=1;char ch=getchar();
while(!isdigit(ch))
{if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void write(long long x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=2505;
int k,n;
int head[N],ver[N],nxt[N],tot;
int c[N],v[N];
int st[N],ed[N],num,re[N<<1],sz[N];
double w[N],f[N][N],sum,s[N<<1][N],d[N<<1];
void add(int x,int y){
ver[++tot]=y;nxt[tot]=head[x];head[x]=tot;
}
void dfs(int x){
sz[x]=1;
st[x]=++num;
for(register int i=head[x];i;i=nxt[i]){
int y=ver[i];
dfs(y);sz[x]+=sz[y];
}
ed[x]=++num;re[num]=x;
}
bool check(double x){
sum=0;
for(register int i=1;i<=n;i++){
w[i]=v[i]-c[i]*x;
d[st[i]]=d[ed[i]]=w[i]/2.0;
sum+=w[i];
s[0][i]=1e9;
for(register int j=1;j<=n-k;j++)
f[i][j]=1e9;
}
for(register int i=1;i<=2*n;i++)
d[i]+=d[i-1];
s[0][0]=0;
double ans=1e9;
for(register int i=1,x;i<=2*n;i++){
for(register int j=0;j<=n-k;j++)
s[i][j]=s[i-1][j];
if(re[i]){//0是不会被踢除的,当然实际上只是作者懒
x=re[i];
f[x][sz[x]]=d[i]-d[st[x]-1];
s[i][sz[x]]=min(s[i][sz[x]],f[x][sz[x]]);
for(register int j=sz[x]+1;j<=n-k;j++){
f[x][j]=min(f[x][j],s[st[x]][j-sz[x]]+d[i]-d[st[x]-1]);
s[i][j]=min(s[i][j],f[x][j]);
}
ans=min(ans,f[re[i]][n-k]);
}
}
return sum-ans>=0;
}
int main(){
k=read();n=read();
for(int i=1,fa;i<=n;i++){
c[i]=read();v[i]=read();
fa=read();
add(fa,i);
}
n++;k++;
dfs(0);
double l=0,r=10000.0;
for(register int i=1;i<=25;i++){
double mid=(l+r)/2.0;
if(check(mid))
l=mid;
else r=mid;
}
printf("%.3lf\n",l);
return 0;
}
本文作者:luckydrawbox
本文链接:https://www.cnblogs.com/luckydrawbox/p/18526469
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步