[WC2010]重建计划

壹、题目描述

传送门 to LUOGU

简要题意:

给你一棵树,树上每条边有个边权 \(w_i\),你需要选一条简单路径 \(S\) 满足 \(L\le |S|\le R\) 并且最大化

\[\sum_{e\in S}w_e\over |S| \]

最后输出这个最大平均值,保留三位小数。

贰、题解

都写成这个形式了,\(01\) 分数规划无疑了。

考虑先二分一个值 \(mid\),那么题目转化为:

将所有边权都减去 \(mid\),检查树上是否存在边权和 \(\ge 0\) 的简单路径,并且路径长度在 \([L,R]\) 之间?

考虑淀粉质,维护一个待选端点集合,枚举一个端点,然后在待选集合中选出符合条件的、边权最大的端点,然后检查这条路径权值和是否 \(\ge 0\),这个可以直接单调队列实现。然后枚举完一个子树再把它们加入备选端点即可。

时间复杂度 \(\mathcal O(n\log^2 n)\).

叁、参考代码

/** @author Arextre; その可憐な少女は魔女であり、旅人でした。 ―― そう、私です。 */

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

#define USING_FREAD
// #define NDEBUG
// #define NCHECK
#include <cassert>

namespace Elaina {

#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
#define y0 FUCK_UP
#define y1 MOTHER_FUCKER
#ifdef NCHECK
# define iputs(Content) ((void)0)
# define iprintf(Content, argvs...) ((void)0)
#else
# define iputs(Content) fprintf(stderr, Content)
# define iprintf(Content, argvs...) fprintf(stderr, Content, argvs)
#endif

    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int, int> pii;
    typedef pair<ll, ll> pll;

    template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
    template<class T> inline void getmin(T& x, const T rhs) { x = min(x, rhs); }
    template<class T> inline void getmax(T& x, const T rhs) { x = max(x, rhs); }

#ifdef USING_FREAD
    inline char qkgetc() {
# define BUFFERSIZE 1 << 20
        static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
        return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
    }
# define CHARRECEI qkgetc()
#else
# define CHARRECEI getchar()
#endif

    template<class T> inline T readret(T x) {
        x = 0; int f = 0; char c;
        while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
        for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
        return f ? -x : x;
    }
    template<class T> inline void readin(T& x) {
        x = 0; int f = 0; char c;
        while (!isdigit(c = CHARRECEI)) if (c == '-') f = 1;
        for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
        if (f) x = -x;
    }
    // special define for int
    inline int readret(int x) {
        x = 0; int f = 0; char c;
        while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
        for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
        return f ? -x : x;
    }
    inline void readin(int& x) {
        x = 0; int f = 0; char c;
        while (!isdigit(c = CHARRECEI)) if (c == '-') f = 1;
        for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
        if (f) x = -x;
    }
    template<class T, class... Args> inline void readin(T& x, Args&... args) {
        readin(x), readin(args...);
    }
    // default enter
    template<class T> inline void writln(T x, char s = '\n') {
        static int fwri_sta[55], fwri_ed = 0;
        if (x < 0) putchar('-'), x = -x;
        do fwri_sta[++fwri_ed] = x % 10, x /= 10; while (x);
        while (putchar(fwri_sta[fwri_ed--] ^ 48), fwri_ed);
        putchar(s);
    }
    // special define for int
    inline void writln(int x, char s = '\n') {
        static int fwri_sta[55], fwri_ed = 0;
        if (x < 0) putchar('-'), x = -x;
        do fwri_sta[++fwri_ed] = x % 10, x /= 10; while (x);
        while (putchar(fwri_sta[fwri_ed--] ^ 48), fwri_ed);
        putchar(s);
    }

} // namespace Elaina
using namespace Elaina;

template<const int MAXSIZE>
struct Tree {

#define Ergodic(T, i, u, v) for (int i = T.tail[u], v; (v = T.e[i].to, i); i = T.e[i].nxt)
    struct edge { int to, nxt, val; } e[MAXSIZE * 2 + 5];
    int tail[MAXSIZE + 5], ecnt;
    inline void add_edge(int u, int v, int w) {
        e[++ecnt] = edge{ v, tail[u], w }; tail[u] = ecnt;
    }
    inline void link(int u, int v, int w) {
        add_edge(u, v, w), add_edge(v, u, w);
    }

};

const int maxn = 1e5;
const double inf = 1e9;

int n, L, R;
Tree<maxn> arc; // the origin tree
Tree<maxn> T;   // the new tree
int rt;

inline void input() {
    readin(n, L, R); int u, v, w;
    rep (i, 2, n) readin(u, v, w), arc.link(u, v, w);
}

namespace rebuild {

    bool used[maxn + 5];
    int max_subtre_siz[maxn + 5], siz[maxn + 5];
    void findrt(int u, int par, int n, int& rt) {
        max_subtre_siz[u] = 0, siz[u] = 1;
        Ergodic (arc, i, u, v) if (!used[v] && v != par) {
            findrt(v, u, n, rt), siz[u] += siz[v];
            getmax(max_subtre_siz[u], siz[v]);
        }
        getmax(max_subtre_siz[u], n - siz[u]);
        if (max_subtre_siz[u] < max_subtre_siz[rt]) rt = u;
    }
    void redfs(int u, int par) {
        siz[u] = 1;
        Ergodic (arc, i, u, v) if (v != par && !used[v])
            redfs(v, u), siz[u] += siz[v];
    }
    int build(int, int);
    void divide(int rt) {
        used[rt] = true; redfs(rt, 0);
        Ergodic (arc, i, rt, v) if (!used[v]) {
            int ret = build(v, siz[v]);
            T.add_edge(rt, ret, 0);
        }
    }

    int build(int u, int n) {
        int rt;
        max_subtre_siz[rt = 0] = n;
        findrt(u, 0, n, rt);
        divide(rt);
        return rt;
    }

} // namespace rebuild

bool vis[maxn + 5];

struct node {
    int to, val, mxdep;
    inline bool operator < (const node& rhs) const {
        return mxdep < rhs.mxdep;
    }
};
vector<node> son[maxn + 5];
void predfs(int u, int par, int dep, int& mx) {
    getmax(mx, dep);
    Ergodic (arc, _, u, v) if (!vis[v] && v != par)
        predfs(v, u, dep + 1, mx);
}
void prelude(int u) {
    vis[u] = true;
    Ergodic (arc, _, u, v) if (!vis[v]) {
        int mxdep = 0;
        predfs(v, u, 1, mxdep);
        son[u].push_back({ v, arc.e[_].val, mxdep });
    }
    sort(son[u].begin(), son[u].end());
    Ergodic (T, _, u, v) prelude(v);
}

bool yes;
double buc[maxn + 5], f[maxn + 5];
void dfs(int u, int par, int dep, double w, double rate) {
    getmax(buc[dep], w);
    Ergodic (arc, i, u, v) if (v != par && !vis[v])
        dfs(v, u, dep + 1, w + arc.e[i].val - rate, rate);
}

inline void work(int u, double rate) {
    static int Q[maxn + 5], op, ed;
    for (int i = 0, siz = son[u].size(), v; i < siz; ++i) {
        v = son[u][i].to;
        dfs(v, u, 1, son[u][i].val - rate, rate);
        if (i > 0) { // not the first son
            op = 1, ed = 0; int p = son[u][i - 1].mxdep;
            for (int j = 1; j <= son[u][i].mxdep; ++j) {
                while (op <= ed && Q[op] > R - j) ++op;
                while (p >= L - j && p > 0) {
                    while (op <= ed && f[Q[ed]] <= f[p]) --ed;
                    Q[++ed] = p, --p;
                }
                if (op <= ed && buc[j] + f[Q[op]] >= 0) {
                    yes = true; break; // break to clear
                }
            }
        }
        rep (j, 1, son[u][i].mxdep) {
            getmax(f[j], buc[j]);
            // from the current node to the sons
            if (L <= j && j <= R && f[j] >= 0) yes = true;
            buc[j] = -inf;
        }
        if (yes) break; // break to clear
    }
    // clear
    if (!son[u].empty())
        rep (i, 1, son[u].back().mxdep) f[i] = -inf;
    if (yes) return;
    return;
}
void solve(int u, double rate) {
    vis[u] = true; work(u, rate);
    if (yes) return void(vis[u] = false);
    Ergodic(T, i, u, v) if (!vis[v]) {
        solve(v, rate);
        if (yes) break;
    }
    return void(vis[u] = false);
}

inline bool check(double rate) {
    yes = false;
    solve(rt, rate);
    return yes;
}

signed main() {
    input();
    rt = rebuild::build(1, n);
    prelude(rt);
    memset(vis + 1, false, n);
    double l = 0, r = 1e6, mid;
    rep (i, 1, n) f[i] = buc[i] = -inf;
    rep (_, 1, 40) {
        mid = (l + r) / 2;
        (check(mid)? l: r) = mid;
    }
    printf("%.3f\n", mid);
    return 0;
}

肆、用到の小 \(\tt trick\)

看到类似平均数、分子求和,分母是类似个数的形式,可以考虑使用 \(01\) 分数规划。

另外,树上路径似乎经常使用淀粉质?

posted @ 2021-03-04 22:13  Arextre  阅读(62)  评论(0编辑  收藏  举报