Luogu P4322 [JSOI2016]最佳团体(0/1分数规划)
用时:写代码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 \times mid
\]
\[\sum a - \sum b \times mid \ge 0
\]
对于每个元素,设它的权值为\(a[i]-b[i]\times mid\),然后就可以转化为一般的\(dp\)或者其他类型的问题了。
对于这道题,套路一下,设每个点的价值为\(p[i]-s[i]\times 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\n",l);
return 0;
}