/* 返回顶部 */

Luogu P4322 [JSOI2016]最佳团体(0/1分数规划)

gate

用时:写代码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;
}

posted @ 2020-06-23 09:44  Mogeko  阅读(124)  评论(0编辑  收藏  举报