ecnuoj 5039 摇钱树

5039. 摇钱树

题目链接:5039. 摇钱树

感觉在赛中的时候,完全没有考虑分数规划这种做法。同时也没有想到怎么拆这两个交和并的式子。有点难受……

当出现分数使其尽量大或者小,并且如果修改其中直接相关的某个值会导致分子分母同时变化的时候,还是要多想想分数规划的做法。

下面引用一下题解

另外这两个交和并的式子,令 \(a = S \and T, b = T – a\),所以原来的式子变成了

\[\frac{|S \and T|}{|S \or T|} = \frac{a}{b + |S|}
\]

所以,用分数规划的做法,二分一个答案 \(ans\),则有

\[\frac{a}{b + |S|} \ge ans \implies a – b \cdot ans \ge |S|\cdot ans
\]

接下来用树上 dp 求一个最大的 \(a – b \cdot ans\) 即可。

\(f_{i,j}\) 表示此时 \(i\) 号点上选了 \(j\) 个子树加和起来最大的 \(a-b\cdot ans\) 值,\(x_{i,j}\) 表示这个式子中的 \(a\)\(y_{i,j}\) 表示这个式子中的 \(b\)

那么接下来就是一个普通的树上背包的转移了,不过区别在于转移完整个 \(f_u\) 后,需要再用取整个以 \(u\) 为根构成的一颗子树去更新一下 \(f_{u,1}\)

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef double db;
typedef long double ld;

#define IL inline
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(), (x).end()
#define dbg1(x) cout << #x << " = " << x << ", "
#define dbg2(x) cout << #x << " = " << x << endl

template<typename Tp> IL void read(Tp &x) {
    x=0; int f=1; char ch=getchar();
    while(!isdigit(ch)) {if(ch == '-') f=-1; ch=getchar();}
    while(isdigit(ch)) { x=x*10+ch-'0'; ch=getchar();}
    x *= f;
}
int buf[42];
template<typename Tp> IL void write(Tp x) {
    int p = 0;
    if(x < 0) { putchar('-'); x=-x;}
    if(x == 0) { putchar('0'); return;}
    while(x) {
        buf[++p] = x % 10;
        x /= 10;
    }
    for(int i=p;i;i--) putchar('0' + buf[i]);
}

const int N = 100000 + 5;
const int M = 50 + 5;
int n, m;
int a[N], suma[N], sz[N], x[N][M], y[N][M];
db f[N][M];
vector<int> G[N];

struct fs {
    int fz, fm;
    int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b);}
    fs(int fz=0, int fm=1) {
        if(fz == 0) this -> fm = 1;
        else {
            int g = gcd(fz, fm);
            this -> fz = fz / g;
            this -> fm = fm / g;
        }
    }
};

int dfs2(int u, int fa, const db& ef_x) {
    int u_leaf = 0;
    if(u != 1 && G[u].size() == 1) {
        if(a[u] == 1) {
            f[u][1] = 1.0;
            x[u][1] = 1; y[u][1] = 0;
        }
        return ++u_leaf;
    }
    for(int i=0;i<=m;i++) f[u][i] = x[u][i] = y[u][i] = 0;
    for(int v : G[u]) {
        if(v == fa) continue;
        int v_leaf = dfs2(v, u, ef_x);
        for(int i=min(u_leaf,m);i>=0;i--) {
            for(int j=1;j<=v_leaf && i+j <= m; j++) {
                if(f[u][i] + f[v][j] > f[u][i+j]) {
                    f[u][i+j] = f[u][i] + f[v][j];
                    x[u][i+j] = x[u][i] + x[v][j];
                    y[u][i+j] = y[u][i] + y[v][j];
                }
            }
        }
        u_leaf += v_leaf;
    }
    if(suma[u] - (sz[u] - suma[u]) * ef_x >= f[u][1]) {
        f[u][1] = suma[u] - (sz[u] - suma[u]) * ef_x;
        x[u][1] = suma[u];
        y[u][1] = sz[u] - suma[u];
    }
    return u_leaf;
}

array<int, 3> check(const db& x) {
    dfs2(1, 0, x);
    int ansu = 1, ansi = 1;
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) {
        if(f[i][j] > f[ansu][ansi]) {
            ansu = i; ansi = j;
        }
    }
    return {f[ansu][ansi] >= suma[1] * x, ansu, ansi};
}

void dfs1(int u, int fa) {
    suma[u] += a[u];
    sz[u] = 1;
    for(int v : G[u]) {
        if(v == fa) continue;
        dfs1(v, u);
        suma[u] += suma[v];
        sz[u] += sz[v];
    }
}

void solve() {
    read(n); read(m);
    for(int i=1;i<=n;i++) read(a[i]);
    for(int i=1;i<n;i++) {
        int u, v; read(u); read(v);
        G[u].pb(v); G[v].pb(u);
    }
    dfs1(1, 0);
    db L = 0.0, R = 1.0;
    fs ans;
    while(R - L >= 1e-10) {
        db M = (L + R) / 2.0;
        auto arr = check(M);
        if(arr[0]) {L = M; ans = fs(x[arr[1]][arr[2]], y[arr[1]][arr[2]] + suma[1]);}
        else R = M;
    }
    write(ans.fz); putchar(32); write(ans.fm); putchar(10);
}

int main() {
#ifdef LOCAL
    freopen("test.in", "r", stdin);
    // freopen("test.out", "w", stdout);
#endif
    int T = 1;
    // read(T);
    while(T--) solve();
    return 0;
}