[WC2010]重建计划
壹、题目描述
简要题意:
给你一棵树,树上每条边有个边权 \(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\) 分数规划。
另外,树上路径似乎经常使用淀粉质?