ecnuoj 5039 摇钱树

yizhihongxing

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

原文链接:https://www.cnblogs.com/bringlu/p/17284738.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:ecnuoj 5039 摇钱树 - Python技术站

(0)
上一篇 2023年4月17日
下一篇 2023年4月17日

相关文章

  • Python实现EM算法实例代码

    EM算法是一种常用的统计学习方法,用于解决含有隐变量的概率模型参数估计问题。在Python中,我们可以使用numpy和scipy等库来实现EM算法。以下是一个完整的攻略,包含了EM算法的实现步骤和例代码。 EM算法的实现步骤 EM算法的实现步骤如下: 定义模型。EM算法适用于含有隐变量的概率模型,需要定义模型的参数和隐变量。 初始化参数。需要对模型的参数进行…

    python 2023年5月14日
    00
  • Python实现的数据结构与算法之快速排序详解

    下面是关于“Python实现的数据结构与算法之快速排序详解”的完整攻略。 1. 快速排序算法概述 快速排序是一种高效的排序算法,它的基本思想是通过分治的想将一个大问题解成多个小问题,后递归地解决这些小问题。快速排序的复杂度为O(nlogn),是一种非高的排序算法。 2 快速排序算法实现 下面使用Python实现快速排序的代码: def quick_sort(…

    python 2023年5月13日
    00
  • 四边形不等式学习笔记

    简要题意 四边形不等式是一种 dp 优化策略。多用于 2D DP。 内容 对于区间 \([l,r]\) 带来的贡献 \(w(l,r)\),如果其满足: 对于 \(L\leq l\leq r \leq R\),\(w(L,r)+w(l,R)\leq w(L,R)+w(l,r)\) 则称 \(w\) 满足四边形不等式。特别地,如果上式符号取等,则称其满足四边形恒…

    算法与数据结构 2023年4月17日
    00
  • python使用三角迭代计算圆周率PI的方法

    下面是详细讲解“Python使用三角迭代计算圆周率PI的方法”的完整攻略。 1. 什么是三角迭代计算圆周率PI的方法? 三角迭代计算圆周率PI的方法是一种使用三角函数计算圆周率的方法。该方法基于圆的周长与直径比值为PI,通过计算正多边形的周长和直径的比值,逐步逼近圆的周长与直径的比值,从而得到圆周率的近似值。 2. Python使用三角迭代计算圆周率PI的方…

    python 2023年5月14日
    00
  • 带你了解Java数据结构和算法之高级排序

    带你了解Java数据结构和算法之高级排序攻略 什么是高级排序算法? 在计算机科学中,排序算法是将一串数据按照特定顺序进行排列的一种算法。根据数据规模、数据类型、稳定性、时间复杂度以及空间复杂度等因素,排序算法分为许多种类。高级排序算法是相对于普通排序算法而言,其时间复杂度更低、排序速度更快、稳定性更高的算法。 高级排序算法的分类及特点 高级排序算法分为内排序…

    数据结构 2023年5月17日
    00
  • 详解数据结构C语言实现之循环队列

    详解数据结构C语言实现之循环队列 什么是循环队列 循环队列是一种数据结构,它可以存储一组固定大小的元素,并且支持在队列尾部插入元素和在队列头部删除元素,当队列尾部没有空间时可以将队列头部空余的位置用来插入元素,实现循环的效果。循环队列的主要优点在于插入和删除元素的时间复杂度均为O(1),而不是O(n)。 如何实现循环队列 循环队列可以使用数组来实现,需要定义…

    数据结构 2023年5月17日
    00
  • C语言数据结构深入探索顺序表

    C语言数据结构深入探索顺序表攻略 一、概述 顺序表是一种线性结构,是计算机程序中最常见的数据结构之一。在C语言中,顺序表可以用数组来实现。本篇文章将深入讲解顺序表的原理和实现方法,帮助读者加深对顺序表的理解,并掌握如何用C语言代码实现顺序表。 二、顺序表的定义和特点 顺序表是指用一组地址连续的存储单元依次存储线性表中的各个元素,用于表示具有相同数据类型的n个…

    数据结构 2023年5月17日
    00
  • Java数据结构及算法实例:插入排序 Insertion Sort

    Java数据结构及算法实例:插入排序 Insertion Sort 算法简介 插入排序是一种简单的排序算法,它的工作方式是每次将一个待排序的元素与前面已经排好序的元素逐个比较,并插入到合适的位置。插入排序的时间复杂度为O(n^2),是一种比较低效的排序算法。 算法实现 以下是使用Java语言实现插入排序算法的代码: public static void in…

    数据结构 2023年5月17日
    00
合作推广
合作推广
分享本页
返回顶部