Description
给定一颗 (n) 个结点的树,第 (i) 个结点上有四个权值 ((k_i, b_i, l_i, r_i))。(Q) 询问,每次给定一个三元组 ((u, v, x)),你需要求出 (max{k_ix+b_i | iin ext{path}(u,v) land xin [l_i, r_i] }) 的值(( ext{path}(u,v)) 表示路径 (u,v) 上的所有结点集合)。如果 ( ext{path}(u,v) = varnothing) 则答案为 (0)。
Hint
(1le n, Qle 10^5,0le k_i, l_i, r_ile 10^6, 0le b_ile 10^{12})。
Solution
牛逼题,但是好卡/kk
发现要求若干个一次函数的最值,基本可以确定为李超树。但是这个题限制太多,朴素方法复杂度及其不可接受:李超树维护线段 (O(log^2U)),加强到区间询问需要线段树套李超树,这题上了树又得树剖,于是一个 (4) 只 (log) 复杂度的算法 横空出世。
考虑这么一个问题:为什么不能用“可持久化”之类的方法让区间询问部分少一只 (log),就像主席树一样?很显然这是因为李超树上的信息不像一般线段树那样具有可差分性。也就是说只能处理前缀的询问,而且还得离线。
首先本题不要求强制在线,那么离线再说。然后观察树剖跳重链的过程:每次找一个(链顶深度大的)往上条至链上方。我们发现,虽然树剖拆解出的区间并不是整个序列的前缀,但却是每个重链从链顶往下的一个前缀。那么尝试拆解询问区间,然后每条重链分开统计对询问的贡献。具体的,对于每个重链,开一颗李超树,然后往下,遇到询问统计答案,遇到线段加入李超树即可。很显然每个结点对应的线段都只被加入一次,那么这部分总的复杂度为 (O(nlog^2 U))。
但是这样上面的算法是不够完善的:每个询问拆解成若干个重链的前缀之后还剩下一段可能不是任何重链前缀的一个残段。也就是说没有任何性质。一个 naive 的想法是一开始的树套树,不过是 (3) 只 (log) 的。如果用分治的方法将区间转化为前缀,然后直接李超树,常数会小不少。然而我们可以做的更好。
思考一下上面那个复杂度离谱在哪里:李超树插入是 (O(log^2U)) 的,而我们要一个点插 (O(log n)) 次,于是初始化线段树部分就爆炸了。但仔细想想,发现一个祖先的信息竟然是完全包含子结点的,这非常难受,于是根据套路我们一开始只在叶子上保留信息,然后一波 dfs 线段树合并上来,这样就是 (O(log^2 U)) 的了。对于询问,只要在 dfs 前拆解到线段树上即可。
最后总复杂度 (O(nlog^2 U)),处处卡满。
Code
遗憾的是我写的代码被卡常了,姑且放一个并不能过的代码:
/*
* Author : _Wallace_
* Source : https://www.cnblogs.com/-Wallace-/
* Problem : LOJ 6679 Unknow
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#include <vector>
inline void read(int& x) {
x = 0; char c; while (!isdigit(c = getchar()));
do x = (x << 1) + (x << 3) + c - '0'; while (isdigit(c = getchar()));
}
inline void read(long long& x) {
x = 0; char c; while (!isdigit(c = getchar()));
do x = (x << 1) + (x << 3) + c - '0'; while (isdigit(c = getchar()));
}
struct Line {
long long k, b;
inline Line(long long k = 0, long long b = 0) : k(k), b(b) { }
};
const int N = 1e5 + 5;
const int U = 1e6 + 5;
const int N_CNT = N << 6;
int n, q;
int L[N], R[N], para[N];
Line dat[N];
std::vector<int> adj[N];
long long ans[N];
struct prefix {
int end, idx;
inline prefix(int end, int idx)
: end(end), idx(idx) { }
};
int fa[N], dep[N], siz[N];
int wtop[N], wson[N];
int dfn[N], rev[N], timer = 0;
std::vector<int> chain[N];
std::vector<prefix> pre[N];
void dfs1(int x, int f) {
dep[x] = dep[fa[x] = f] + 1, siz[x] = 1;
for (auto y : adj[x]) if (y != f) {
dfs1(y, x), siz[x] += siz[y];
if (siz[y] > siz[wson[x]]) wson[x] = y;
}
}
void dfs2(int x, int t) {
chain[wtop[x] = t].push_back(x);
rev[dfn[x] = ++timer] = x;
if (wson[x]) dfs2(wson[x], t);
for (auto y : adj[x]) if (y != fa[x] && y != wson[x])
dfs2(y, y);
}
#define mid ((l + r) >> 1)
int lc[N_CNT], rc[N_CNT], total = 0;
Line best[N_CNT];
int bin[N_CNT], btop = 0;
inline void recycle(int x) {
bin[++btop] = x;
lc[x] = rc[x] = 0, best[x] = Line();
}
inline int create() {
return btop ? bin[btop--] : ++total;
}
inline void reset() {
for (int i = 1; i <= total; i++) recycle(i);
}
void insert(int& x, int l, int r, int ql, int qr, Line cur) {
if (ql > r || l > qr) return;
if (!x) x = create();
if (ql <= l && r <= qr) {
Line& old = best[x];
long long ov = old.k * mid + old.b;
long long cv = cur.k * mid + cur.b;
if (l == r) {
if (ov < cv) old = cur;
} else {
if (old.k < cur.k) {
if (cv < ov) insert(rc[x], mid + 1, r, ql, qr, cur);
else insert(lc[x], l, mid, ql, qr, old), old = cur;
} else if (old.k > cur.k) {
if (cv < ov) insert(lc[x], l, mid, ql, qr, cur);
else insert(rc[x], mid + 1, r, ql, qr, old), old = cur;
} else if (old.b < cur.b) old = cur;
}
} else {
insert(lc[x], l, mid, ql, qr, cur);
insert(rc[x], mid + 1, r, ql, qr, cur);
}
}
long long query(int x, int l, int r, int p) {
if (!x) return 0ll;
long long val = best[x].k * p + best[x].b;
if (l == r) return val;
if (p <= mid) return std::max(query(lc[x], l, mid, p), val);
else return std::max(query(rc[x], mid + 1, r, p), val);
}
int merge(int x, int y, int l, int r) {
if (!x || !y) return x | y;
insert(x, l, r, l, r, best[y]);
if (l != r) {
lc[x] = merge(lc[x], lc[y], l, mid);
rc[x] = merge(rc[x], rc[y], mid + 1, r);
}
return recycle(y), x;
}
std::vector<int> rec[N << 2];
int root[N << 2];
void apply(int x, int l, int r, int ql, int qr, int idx) {
if (ql <= l && r <= qr) return rec[x].push_back(idx);
if (ql > r || l > qr) return;
apply(x << 1, l, mid, ql, qr, idx);
apply(x << 1 | 1, mid + 1, r, ql, qr, idx);
}
void solve_range(int x, int l, int r) {
if (l != r) {
solve_range(x << 1, l, mid);
solve_range(x << 1 | 1, mid + 1, r);
root[x] = merge(root[x << 1], root[x << 1 | 1], 0, U);
} else insert(root[x], 0, U, L[rev[l]], R[rev[l]], dat[rev[l]]);
for (auto idx : rec[x])
ans[idx] = std::max(ans[idx], query(root[x], 0, U, para[idx]));
}
#undef mid
signed main() {
read(n), read(q);
for (int i = 1; i <= n; i++)
read(dat[i].k), read(dat[i].b), read(L[i]), read(R[i]);
for (int i = 1, u, v; i < n; i++)
read(u), read(v), adj[u].push_back(v), adj[v].push_back(u);
dfs1(1, 0), dfs2(1, 1);
for (int i = 1, u, v; i <= q; i++) {
read(u), read(v), read(para[i]);
for (; wtop[u] != wtop[v]; u = fa[wtop[u]]) {
if (dep[wtop[u]] < dep[wtop[v]]) std::swap(u, v);
pre[wtop[u]].emplace_back(u, i);
}
if (dep[u] < dep[v]) std::swap(u, v);
apply(1, 1, n, dfn[v], dfn[u], i);
}
for (int i = 1; i <= n; i++) if (i == wtop[i]) {
auto it = chain[i].begin();
int root = 0;
std::sort(pre[i].begin(), pre[i].end(), [&](const prefix& i, const prefix& j) {
return dfn[i.end] < dfn[j.end];
});
for (auto req : pre[i]) {
for (; it != chain[i].end() && dfn[req.end] >= dfn[*it]; it++)
insert(root, 0, U, L[*it], R[*it], dat[*it]);
ans[req.idx] = std::max(ans[req.idx], query(root, 0, U, para[req.idx]));
}
reset();
}
solve_range(1, 1, n);
for (int i = 1; i <= q; i++)
printf("%lld
", ans[i]);
return 0;
}