用时:写代码20min,debug+调emacs+卡常60min
因为就是想找个0/1分数规划题,直接标签搜索的,所以没有思考时间...
做2019各省省选的时候看到某个ac自动机+dp+0/1分数规划,想起来好像还没写过0/1分数规划...找个模板写一下。
(0/1)分数规划
有若干个元素,每个元素有(a,b)两个属性,选出其中一些元素,求 (max(frac{sum a}{sum b}))
显然答案具有可二分性。设这个最大值为(mid),则对于每个元素,可以把原式转化为:
[frac{sum a}{sum b} ge mid
]
[sum a ge sum b imes mid
]
[sum a - sum b imes mid ge 0
]
对于每个元素,设它的权值为(a[i]-b[i] imes mid),然后就可以转化为一般的(dp)或者其他类型的问题了。
对于这道题,套路一下,设每个点的价值为(p[i]-s[i] imes mid),费用为(1),做树上背包即可。
[f[u][i] = max(f[u][i-j] + f[v][j]
]
注意
- (0)必须选但不算人数,所以总容量(k++)。
- 父亲节点必须选,所以(i)的大小为((1,k)),(j)的大小为((0,i-1))。
- 可能出现负数,一开始(0)以外的容量全赋为(-INF)。
卡常
一开始(TLE 50),改了半天(TLE 90),我是傻逼,还是(O2)吧
- 记录子树大小(siz[u]),上限改为(min(siz[u],k))能减少很多无意义计算,会快很多。(v)同理。
- 数组不能太大,(eps)不能太小,别用(memset())...
(code)
#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#define Mogeko qwq
using namespace std;
const int maxn = 2505;
const int INF = 0x3f3f3f3f;
const int R = 1e4;
const double eps = 1e-4;
int m,n,j,cnt;
int head[maxn],to[maxn],nxt[maxn],siz[maxn];
double s[maxn],p[maxn],f[maxn][maxn];
double l,r,mid;
int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch < '0' || ch > '9'){
if(ch == '-') f = -1;
ch = getchar();
}
while('0' <= ch && ch <= '9'){
x = x*10 + ch-'0';
ch = getchar();
}
return x*f;
}
void add(int x,int y){
to[++cnt] = y;
nxt[cnt] = head[x];
head[x] = cnt;
}
void dfs(int u){
//p/s=mid,p=s*mid,p-s*mid=0
f[u][1] = p[u]-s[u]*mid;
siz[u] = 1;
for(int i = head[u];i;i = nxt[i]){
int v = to[i];
dfs(v);
siz[u] += siz[v];
for(int j = min(siz[u],m);j >= 1;j--)
for(int k = 0;k <= min(siz[v],j-1);k++)
f[u][j] = max(f[u][j],f[u][j-k] + f[v][k]);
}
}
bool check(){
for(int i = 0;i <= n;i++)
for(int j = 1;j <= m;j++)
f[i][j] = -INF;
dfs(0);
return f[0][m] >= 0;
}
int main(){
m = read(),n = read();
m++;
for(int i = 1;i <= n;i++){
s[i] = read(),p[i] = read();
add(read(),i);
}
l = 0,r = R;
while(r-l >= eps){
mid = (l+r)*0.5;
if(check()) l = mid;
else r = mid;
}
printf("%.3lf
",l);
return 0;
}