Loading

【题解】P6074 最小路径

太弱小了,失去了力量和精神。

思路

01 分数规划 + 长链剖分优化 dp.

首先求形如 \(\frac{\sum\limits a_i}{\sum\limits b_i}\) 式子的最值,首先想到二分答案 \(ans\),令每个结点的权值为 \(a_i - ans b_i\),问题转化成求树上的最短链。

可以设 \(f[u][i]\) 表示从结点 \(u\) 向下走 \(i\) 步得到的最短链长度,显然有 \(f[u][i] = \min\limits_{v \in son(u)} f[v][i - 1] + w(u)\),其中 \(w(u) = a_u - ans b_u\)

由于问题是静态的,于是可以考虑写成树上差分的形式。令 \(s_u\) 为从根到 \(u\) 路径的 \(w\) 之和,\(f[u][i]\) 改为从根结点到结点 \(u\)\(i\) 级子结点的最短链长度,显然有:

\(f[u][i] = \min\limits_{v \in son(u)} f[v][i - 1], f[u][0] = s_u\)

那么这个式子可以用长链剖分优化。

因为不可能记录下所有的状态,所以要考虑用指针的形式写。

具体的流程是:

  1. 遍历当前结点的深子结点,直接将状态接到当前结点的答案中

  2. 遍历当前结点的其他子结点,暴力合并

  3. 统计答案

因为每条深链只会在链首的父结点被合并一次,所以复杂度是所有深链的长度之和,即 \(O(n)\),magic!!!1

代码

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;

const int maxn = 2e5 + 5;
const double eps = 1e-4;
const double inf = 1e18;

int n, m;
int len[maxn], son[maxn], fa[maxn], a[maxn], b[maxn];
double res;
double v[maxn], s[maxn], *f[maxn], *cur;
vector<int> g[maxn];

void dfs(int u, int f)
{
    fa[u] = f;
    for (int v : g[u])
    {
        if (v == f) continue;
        dfs(v, u);
        if (len[v] > len[son[u]]) son[u] = v;
    }
    len[u] = len[son[u]] + 1;
}

void dp(int u, int pre)
{
    v[u] += v[pre];
    if (son[u])
    {
        f[son[u]] = f[u] + 1;
        dp(son[u], u);
    }
    f[u][0] = v[u];
    for (int to : g[u])
    {
        if ((to == pre) || (to == son[u])) continue;
        f[to] = cur, cur += len[to];
        dp(to, u);
        for (int j = 0; j < len[to]; j++)
            if ((m - j - 1 >= 0) && (m - j - 1 < len[u])) res = min(res, f[u][m - j - 1] + f[to][j] - v[u] - v[pre]);
        for (int j = 0; j < len[to]; j++) f[u][j + 1] = min(f[u][j + 1], f[to][j]);
    }
    if (m < len[u]) res = min(res, f[u][m] - v[pre]);
}

bool check(double k)
{
    res = inf;
    for (int i = 1; i <= n; i++) s[i] = inf;
    for (int i = 1; i <= n; i++) v[i] = a[i] - k * b[i];
    f[1] = cur = s, cur += len[1];
    dp(1, 0);
    return (res <= 0);
}

int main()
{
    // freopen("P6074_1.in", "r", stdin);
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) scanf("%d", &b[i]);
    for (int i = 1, u, v; i <= n - 1; i++)
    {
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, 0);
    double l = 0, r = 2023.0, res = -1;
    while (l <= r - eps)
    {
        double mid = (l + r) / 2.0;
        if (check(mid)) res = mid, r = mid - eps;
        else l = mid + eps;
    }
    if (res < 0) puts("-1");
    else printf("%.2lf\n", res);
    return 0;
}
posted @ 2023-01-07 16:06  kymru  阅读(32)  评论(0编辑  收藏  举报