前言

刚开始看着两道题感觉头皮发麻,后来看看题解,发现挺好理解,只是代码有点长。


BZOJ 3672[NOI2014]购票

  • 中文题面,题意略: ​​BZOJ 3672[NOI2014]购票​
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分

  • 表示
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分_02

  • 点所花的最小费用,可以写出方程式
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_线段树_03

  • 其中
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分_04

  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分_02

  • 的祖先且
  • 显然可以斜率优化。那么我们来想想如何在树上做斜率优化。方法就是树链剖分后用
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_线段树_06

  • 序建一颗线段树。线段树的每一个节点上用
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分_07

  • 维护这个区间中所有点形成的下凸包。从根往下
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_线段树_08

  • ,在
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_线段树_08

  • 同时维护一个栈来存从根到当前点
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_线段树_10

  • 的链上的点,这些点都有可能转移到当前点。那么只需要在这条链上二分出深度最小且满足点
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分_11

  • ,在线段树中
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_斜率优化_12

  • 的所有凸包里,二分查询答案就行。树链剖分+线段树+凸包二分,时间复杂度
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_斜率优化_13

CODE

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 200005;
const LL inf = 1e18;
int n, fa[MAXN], S[MAXN], indx;
int fir[MAXN], to[MAXN], w[MAXN], nxt[MAXN], cnt;
LL dis[MAXN], f[MAXN], P[MAXN], Q[MAXN], L[MAXN];
vector<int> t[MAXN<<2];
int sz[MAXN], hson[MAXN], top[MAXN], dfn[MAXN], seq[MAXN], tmr;

template<typename T>inline void read(T &num) {
char ch; while((ch=getchar())<'0'||ch>'9');
for(num=0;ch>='0'&&ch<='9';num=num*10+ch-'0',ch=getchar());
}
inline void Addedge(int u, int v, int wt) {
to[++cnt] = v; nxt[cnt] = fir[u]; fir[u] = cnt; w[cnt] = wt;
}

inline void dfs(int u) {
sz[u] = 1;
for(int i = fir[u]; i; i = nxt[i]) {
dis[to[i]] = dis[u] + w[i];
dfs(to[i]); sz[u] += sz[to[i]];
if(sz[to[i]] > sz[hson[u]]) hson[u] = to[i];
}
}
inline void dfs2(int u, int tp) {
top[u] = tp; dfn[u] = ++tmr; seq[tmr] = u;
if(hson[u]) dfs2(hson[u], tp);
for(int i = fir[u]; i; i = nxt[i])
if(to[i] != hson[u]) dfs2(to[i], to[i]);
}

inline double Slope(int i, int j) {
return (double)(f[i] - f[j]) / (dis[i] - dis[j]);
}

inline void Modify(int i, int l, int r, int x) {
int sz = t[i].size();
while(sz > 1 && Slope(seq[x], t[i][sz-2]) < Slope(t[i][sz-1], t[i][sz-2]))
t[i].pop_back(), --sz;
t[i].push_back(seq[x]);
if(l == r) return;
int mid = (l + r) >> 1;
if(x <= mid) Modify(i<<1, l, mid, x);
else Modify(i<<1|1, mid+1, r, x);
}

inline LL calc(vector<int>t, int i) {
int l = 1, r = t.size()-1, mid, pos = 0;
while(l <= r) {
mid = (l + r + 1) >> 1;
if(Slope(t[mid], t[mid-1]) < 1.0*P[i]) pos = mid, l = mid+1;
else r = mid-1;
}
int j = t[pos];
return f[j] + (dis[i]-dis[j])*P[i] + Q[i];
}

inline LL Query(int i, int l, int r, int x, int y, int id) {
if(x <= l && r <= y) return calc(t[i], id);
int mid = (l + r) >> 1; LL res = inf;
if(x <= mid) res = min(res, Query(i<<1, l, mid, x, y, id));
if(y > mid) res = min(res, Query(i<<1|1, mid+1, r, x, y, id));
return res;
}

inline void solve(int i, int anc) {
int u = fa[i]; f[i] = inf;
while(top[u] != top[anc])
f[i] = min(f[i], Query(1, 1, n, dfn[top[u]], dfn[u], i)), u = fa[top[u]];
f[i] = min(f[i], Query(1, 1, n, dfn[anc], dfn[u], i));
}

inline void dp(int u) {
S[++indx] = u;
if(u > 1) {
int l = 1, r = indx-1, mid;
while(l < r) {
mid = (l + r) >> 1;
if(dis[u] - dis[S[mid]] <= L[u]) r = mid;
else l = mid+1;
}
solve(u, S[l]);
}
Modify(1, 1, n, dfn[u]);
for(int i = fir[u]; i; i = nxt[i]) dp(to[i]);
--indx;
}

int main () {
int type;
read(n), read(type);
for(int i = 2, x; i <= n; ++i) {
read(fa[i]), read(x), read(P[i]), read(Q[i]), read(L[i]);
Addedge(fa[i], i, x);
}
dfs(1); dfs2(1, 1); dp(1);
for(int i = 2; i <= n; ++i)
printf("%lld\n", f[i]);
}


BZOJ 2402 陶陶的难题II

  • 题意略: ​​2402: 陶陶的难题II​
  • 想想在长度为
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_斜率优化_14

  • 序列上如何求这个最大值。暴力是
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_线段树_15

  • 的。
    设最终得到最大比值为
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分_16

  • 。那么就有
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_线段树_17

  • 对任意
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_斜率优化_18

  • 都满足,且存在至少一组
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_斜率优化_18

  • 使等式取等。
  • 设等式左边的最大值为
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分_20

  • ,那么对于任意取值
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_斜率优化_21

  • ,有
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分_22

  • 显然我们可以二分,每次求最大值
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分_20

  • 就行了。那么等式左边的最大值只用分别算
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_斜率优化_24

  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分_25

  • 的最大值再加起来。计算最大值时就是用斜率优化,维护一个上凸包。在凸包上二分求最值就行了。
  • 而转移到树上就像上一道题一样在
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_线段树_06

  • 序上用线段树维护凸包就行了。
  • 这道题
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_斜率优化_27

  • 线段树时需要把子树上传来的两个凸包合并,注意往凸包里加点时要保证
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_线段树_28

  • 坐标递增。
  • 时间复杂度
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_树链剖分_29

  • ,能过真是奇迹(听说树链跑不满+凸包上的点少?)

CODE

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 300005;
const double inf = 1e16;
const double eps = 1e-10;

int n; double x[MAXN][2], y[MAXN][2];
int to[MAXN<<1], nxt[MAXN<<1], fir[MAXN], cnt;
int sz[MAXN], top[MAXN], fa[MAXN], hson[MAXN], dfn[MAXN], tmr, seq[MAXN], dep[MAXN];

inline void read(int &num) {
char ch; while((ch=getchar())<'0'||ch>'9');
for(num=0;ch>='0'&&ch<='9';num=num*10+ch-'0',ch=getchar());
}
inline void addedge(int u, int v) { to[++cnt] = v; nxt[cnt] = fir[u]; fir[u] = cnt; }
void dfs(int u, int ff) {
fa[u] = ff; sz[u] = 1;
dep[u] = dep[fa[u]] + 1;
for(int i = fir[u]; i; i = nxt[i])
if(to[i] != fa[u]) {
dfs(to[i], u), sz[u] += sz[to[i]];
if(sz[to[i]] > sz[hson[u]]) hson[u] = to[i];
}
}
void dfs2(int u, int tp) {
top[u] = tp; dfn[u] = ++tmr; seq[tmr] = u;
if(hson[u]) dfs2(hson[u], tp);
for(int i = fir[u]; i; i = nxt[i])
if(to[i] != fa[u] && to[i] != hson[u])
dfs2(to[i], to[i]);
}
struct SegmentTree {
vector<int> vec[MAXN<<2]; bool flg;
inline double slope(const int &i, const int &j) {
return (y[i][flg]-y[j][flg]) / (x[i][flg]-x[j][flg]);
}
inline bool Turn_left(const int &i, const int &j, const int &k) {
register double a = x[j][flg] - x[i][flg], b = y[j][flg] - y[i][flg];
register double c = x[k][flg] - x[i][flg], d = y[k][flg] - y[i][flg];
return a*d + eps > b*c;
}
inline void Merge(vector<int> &h, const vector<int> &h1, const vector<int> &h2) {
vector<int>::const_iterator i, j;
i = h1.begin(), j = h2.begin();
int top = 0;
while(i != h1.end() || j != h2.end()) { //下面的比较大小就是保证x递增
int p = i == h1.end() ? *j++ : j == h2.end() ? *i++ : x[*i][flg] < x[*j][flg] ? *i++ : *j++;
while(top >= 2 && Turn_left(h[top-2], h[top-1], p)) h.pop_back(), --top;
h.push_back(p), ++top;
}
}
void build(int i, int l, int r) {
if(l == r) { vec[i].push_back(seq[l]); return; }
register int mid = (l + r) >> 1;
build(i<<1, l, mid);
build(i<<1|1, mid+1, r);
Merge(vec[i], vec[i<<1], vec[i<<1|1]);
}
inline double calc(const vector<int> &V, const double &now) {
register int l = 1, r = V.size()-1, mid, pos = V[0];
while(l <= r) {
mid = (l + r) >> 1;
if(slope(V[mid-1], V[mid]) + eps > now) pos = V[mid], l = mid+1;
else r = mid-1;
}
return y[pos][flg] - now*x[pos][flg];
}
double Query(const int &i, const int &l, const int &r, const int &L, const int &R, const double &now) {
if(L <= l && r <= R) return calc(vec[i], now);
int mid = (l + r) >> 1; register double res = -inf;
if(L <= mid) res = max(res, Query(i<<1, l, mid, L, R, now));
if(R > mid) res = max(res, Query(i<<1|1, mid+1, r, L, R, now));
return res;
}
}T[2];

inline double check(int x, int y, const double &now, const bool &flg) {
register double res = -inf;
register int fx = top[x], fy = top[y];
while(fx != fy) {
if(dep[fx] < dep[fy]) swap(x, y), swap(fx, fy);
res = max(res, T[flg].Query(1, 1, n, dfn[top[x]], dfn[x], now));
x = fa[fx], fx = top[x];
}
if(dep[x] < dep[y]) swap(x, y);
res = max(res, T[flg].Query(1, 1, n, dfn[y], dfn[x], now));
return res;
}
inline int lca(int u, int v) {
while(top[u] != top[v]) {
if(dep[top[u]] > dep[top[v]]) u = fa[top[u]];
else v = fa[top[v]];
}
return dep[u] > dep[v] ? v : u;
}
inline bool cmp0(const int &i, const int &j) { return x[i][0] < x[j][0]; }
inline bool cmp1(const int &i, const int &j) { return x[i][1] < x[j][1]; }
inline int dcmp(double x) {
if(fabs(x) < eps) return 0;
if(x > 0) return 1;
return -1;
}
int main () {
read(n); T[1].flg = 1;
for(int i = 1; i <= n; ++i) scanf("%lf", &x[i][0]);
for(int i = 1; i <= n; ++i) scanf("%lf", &y[i][0]);
for(int i = 1; i <= n; ++i) scanf("%lf", &x[i][1]);
for(int i = 1; i <= n; ++i) scanf("%lf", &y[i][1]);
for(int i = 1, a, b; i < n; ++i)
read(a), read(b), addedge(a, b), addedge(b, a);
dfs(1, 0); dfs2(1, 1);
T[0].build(1, 1, n);
T[1].build(1, 1, n);
int m, a, b;
read(m);
while(m--){
read(a), read(b);
double l = 0, r = 1e8, mid;
while(r - l > 1e-5) {
mid = (l + r) / 2;
if(dcmp(check(a, b, mid, 0) + check(a, b, mid, 1)) >= 0) l = mid;
else r = mid;
}
printf("%.5f\n", l);
}
}
  • 本人是大常数选手,
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_线段树_30

  • 卡过
  • BZOJ 3672[NOI2014]购票(树链剖分+线段树维护凸包+斜率优化) + BZOJ 2402 陶陶的难题II (树链剖分+线段树维护凸包+分数规划+斜率优化)_斜率优化_31