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 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;
}

原文地址:https://www.cnblogs.com/mogeko/p/13180432.html