1. 程式人生 > >[BZOJ4753][Jsoi2016]最佳團體

[BZOJ4753][Jsoi2016]最佳團體

bsp out blog ans cnblogs close -s output getc

Description

JSOI信息學代表隊一共有N名候選人,這些候選人從1到N編號。方便起見,JYY的編號是0號。每個候選人都由一位 編號比他小的候選人Ri推薦。如果Ri=0則說明這個候選人是JYY自己看上的。為了保證團隊的和諧,JYY需要保證, 如果招募了候選人i,那麽候選人Ri"也一定需要在團隊中。當然了,JYY自己總是在團隊裏的。每一個候選人都有 一個戰鬥值Pi",也有一個招募費用Si"。JYY希望招募K個候選人(JYY自己不算),組成一個性價比最高的團隊。 也就是,這K個被JYY選擇的候選人的總戰鬥值與總招募總費用的比值最大。

Input

輸入一行包含兩個正整數K和N。 接下來N行,其中第i行包含3個整數Si,Pi,Ri表示候選人i的招募費用,戰鬥值和推薦人編號。 對於100%的數據滿足1≤K≤N≤2500,0<"Si,Pi"≤10^4,0≤Ri<i

Output

輸出一行一個實數,表示最佳比值。答案保留三位小數。

Sample Input

1 2
1000 1 0
1 1000 1

Sample Output

0.001
看到有分數那麽可能是二分答案$mid$。 如果$\frac{\sum P_i}{\sum S_i}> mid$,即$\sum P_i-mid \times \sum S_i > 0$,那麽l就應該增大。 這樣我們可以把每個點的權值看成$P_i - S_i \times mid$, 然後做樹形背包看最後的最大值是否大於0就行了。 樹形背包細節太多了...調了一節課... 技術分享圖片
#include <iostream>
#include 
<cstdio> #include <cstring> #include <algorithm> using namespace std; #define reg register inline int read() { int res = 0;char ch=getchar();bool fu=0; while(!isdigit(ch))fu|=(ch==-),ch=getchar(); while(isdigit(ch)) res=(res<<3)+(res<<1)+(ch^48),ch=getchar();
return fu?-res:res; } #define N 2505 int K, n; int P[N], S[N]; struct edge { int nxt, to; }ed[N]; int head[N], cnt; inline void add(int x, int y) { ed[++cnt] = (edge){head[x], y}; head[x] = cnt; } double f[N][N], val[N]; int siz[N], dfn[N], tot; void dfs(int x) { siz[x] = 1; for (reg int i = head[x] ; i ; i = ed[i].nxt) { int to = ed[i].to; dfs(to); siz[x] += siz[to]; } dfn[x] = ++tot; } void dp(int x) { siz[x] = 1; for (reg int i = head[x] ; i ; i = ed[i].nxt) { int to = ed[i].to; dp(to); for (reg int j = siz[x] ; j >= 1 ; j --) for (reg int p = 0 ; p <= siz[to] ; p ++) f[x][j + p] = max(f[x][j + p], f[to][p] + f[x][j]); siz[x] += siz[to]; } } int main() { K = read() + 1, n = read(); for (reg int i = 1 ; i <= n ; i ++) S[i] = read(), P[i] = read(), add(read(), i); dfs(0); double l = 0, r = 1e4, mid(0); while(r - l >= 1e-5) { mid = (l + r) / 2; for (reg int i = 1 ; i <= n ; i ++) val[i] = 1.0 * P[i] - 1.0 * S[i] * mid; for (reg int i = 0 ; i <= n ; i ++) for (reg int j = 0 ; j <= K ; j ++) f[i][j] = -1e9; for (reg int i = 0 ; i <= n ; i ++) f[i][0] = 0; for (reg int i = 0 ; i <= n ; i ++) f[i][1] = val[i]; dp(0); if (f[0][K] >= 1e-5) l = mid; else r = mid; } printf("%.3lf\n", l); return 0; }
樹形背包

對於這種有依賴的樹形背包,有一種比較常見的優化方法,這種方法只是針對這種選擇一個點必須選擇其父親的樹上背包問題。

借鑒自一個巨佬的博客。

我們求出這棵樹的後序遍歷,對於一個點的dfs序是$i$,那麽它的子樹一定是$[i - siz[x], i]$的一段連續區間,枚舉這個點選不選,選的話從$f[i-1]$轉移過來,不選的話從$f[i-siz[i]]$轉移過來,復雜度$O(NM)$


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define reg register
inline int read() {
    int res = 0;char ch=getchar();bool fu=0;
    while(!isdigit(ch))fu|=(ch==-),ch=getchar();
    while(isdigit(ch)) res=(res<<3)+(res<<1)+(ch^48),ch=getchar();
    return fu?-res:res;
}
#define N 2505
int K, n;
int P[N], S[N];

struct edge {
    int nxt, to;
}ed[N];
int head[N], cnt;
inline void add(int x, int y) {
    ed[++cnt] = (edge){head[x], y};
    head[x] = cnt;
}

double f[N][N], val[N];
int siz[N], dfn[N], tot;

void dfs(int x)
{
    siz[x] = 1;
    for (reg int i = head[x] ; i ; i = ed[i].nxt)
    {
        int to = ed[i].to;
        dfs(to);
        siz[x] += siz[to];
    }
    dfn[x] = ++tot;
}

void dp(int x)
{
    siz[x] = 1;
    for (reg int i = head[x] ; i ; i = ed[i].nxt)
    {
        int to = ed[i].to;
        dp(to);
        siz[x] += siz[to];
    }
    for (reg int j = 1 ; j <= K ; j ++)
        f[dfn[x]][j] = max(f[dfn[x] - siz[x]][j], f[dfn[x] - 1][j - 1] + val[x]);    
}

int main()
{
    K = read() + 1, n = read();
    for (reg int i = 1 ; i <= n ; i ++) S[i] = read(), P[i] = read(), add(read(), i);
    dfs(0);
    double l = 0, r = 1e7, mid(0);
    while(r - l >= 1e-6)
    {
        mid = (l + r) / 2;
        for (reg int i = 0 ; i <= n ; i ++) 
        {
            f[i][0] = 0;
            for (reg int j = 1 ; j <= K ; j ++)
                f[i][j] = -1e9;
        }    
        for (reg int i = 1 ; i <= n ; i ++) val[i] = 1.0 * P[i] - 1.0 * S[i] * mid;
        dp(0);
        if (f[tot][K] >= 1e-6) l = mid;
        else r = mid;
    }
    printf("%.3lf\n", l);
    return 0;
}

[BZOJ4753][Jsoi2016]最佳團體