题目大意:
给出一棵以
0
0
0为根的树,每个节点有两个信息
a
i
,
b
i
a_i,b_i
ai,bi。其中
a
0
=
b
0
=
0
a_0=b_0=0
a0=b0=0。
现在已选
0
0
0号节点,要再选出
K
K
K个节点,使得
Σ
i
=
1
k
a
i
Σ
i
=
1
k
b
i
\huge \frac {\Sigma^{k}_{i=1} a_i} {\Sigma^{k}_{i=1}b_i}
Σi=1kbiΣi=1kai最大。
要求:若选出节点
i
i
i,则它的父亲
f
a
t
h
e
r
[
i
]
father[i]
father[i]也必须被选。
解题方法
看到
Σ
i
=
1
k
a
i
Σ
i
=
1
k
b
i
\huge \frac {\Sigma^{k}_{i=1} a_i} {\Sigma^{k}_{i=1}b_i}
Σi=1kbiΣi=1kai就想到用分数规划
q
w
q
qwq
qwq。
二分答案,假设当前值为
m
i
d
mid
mid,那么只需验证是否有
Σ
i
=
1
k
a
i
Σ
i
=
1
k
b
i
\huge \frac {\Sigma^{k}_{i=1} a_i} {\Sigma^{k}_{i=1}b_i}
Σi=1kbiΣi=1kai
>
=
m
i
d
>=mid
>=mid即可。
变形一下可得
Σ
a
i
−
m
i
d
∗
Σ
b
i
\large \Sigma a_i-mid*\Sigma b_i
Σai−mid∗Σbi
>
=
0
>=0
>=0。
于是让每个点的权值为
a
i
−
m
i
d
∗
b
i
a_i-mid*b_i
ai−mid∗bi,在树上跑
d
p
dp
dp:
令
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示第
i
i
i个点的子树内,取
包括i的
{\color{red}\text{包括i的}}
包括i的
j
j
j个节点的最大权值和。
转移比较显然,扫一遍子节点,大力转移即可。
但是要注意每次算出来的答案不能直接记入
d
p
dp
dp数组,不然会把前一个子节点的
d
p
dp
dp值覆盖掉(体会一下),然后玄学
W
A
WA
WA。
珂以用一个
t
m
p
tmp
tmp数组来保存临时答案,最后再记录进
d
p
dp
dp数组。
转移方程(
n
x
t
nxt
nxt表示
x
x
x的一个子节点):
t
m
p
[
j
]
=
m
a
x
(
t
m
p
[
j
]
,
d
p
[
x
]
[
j
−
k
]
+
d
p
[
n
x
t
]
[
k
]
)
tmp[j]=max(tmp[j],dp[x][j-k]+dp[nxt][k])
tmp[j]=max(tmp[j],dp[x][j−k]+dp[nxt][k])
并且要注意
j
,
k
j,k
j,k的范围。
Ps.
m
e
m
s
e
t
memset
memset的一个小技巧:
把double数组重置成
−
I
N
F
-INF
−INF的方法:
memset(a,0xf0,sizeof(a));
丑陋无比的代码:
注:这段代码在bzoj上跑不过quq,请各位dalao谨慎食用
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<math.h>
#define re register int
#define rl register ll
using namespace std;
typedef long long ll;
int read() {
re x=0,f=1;
char ch=getchar();
while(ch<'0' || ch>'9') {
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0' && ch<='9') {
x=10*x+ch-'0';
ch=getchar();
}
return x*f;
}
const int Size=2605;
const double eps=1e-4;
int n,k,a[Size],b[Size],f[Size];
int cnt,head[Size],siz[Size];
struct Edge {
int v,next;
} w[Size<<1];
void AddEdge(int u,int v) {
w[++cnt].v=v;
w[cnt].next=head[u];
head[u]=cnt;
}
double val[Size];
double dp[Size][Size];
double tmp[Size];
void dfs(int x) {
//dp[i][j]:在i的子树中取j个点的最大值
siz[x]=1;
dp[x][1]=val[x];
dp[x][0]=0;
for(int i=head[x]; i; i=w[i].next) {
int nxt=w[i].v;
dfs(nxt);
memset(tmp,0xf0,sizeof(tmp));
siz[x]+=siz[nxt];
for(re j=1; j<=siz[x]; j++) {
int maxk=min(j-1,siz[nxt]);
for(re k=0; k<=maxk; k++) {
double now=dp[x][j-k]+dp[nxt][k];
if(now>tmp[j]) {
tmp[j]=now;
}
}
}
for(re j=1; j<=siz[x]; j++) {
dp[x][j]=tmp[j];
}
}
}
bool check(double mid) {
for(re i=1; i<=n; i++) {
val[i]=(double)a[i]-mid*b[i];
}
memset(dp,0xf0,sizeof(dp));
dfs(0);
return dp[0][k+1]>0;
}
int main() {
k=read();
n=read();
double l=0,r=0,mid;
for(re i=1; i<=n; i++) {
b[i]=read();
a[i]=read();
f[i]=read();
AddEdge(f[i],i);
double now=1.0*a[i]/b[i];
if(now>r) {
r=now;
}
}
while(l+eps<=r) {
mid=(l+r)/2;
if(check(mid)) {
l=mid;
} else {
r=mid;
}
}
printf("%.3f",l);
return 0;
}