[BZOJ3672][UOJ#7][NOI2014]购票
[BZOJ3672][UOJ#7][NOI2014]购票
试题描述
输入
第 1 行包含2个非负整数 n,t,分别表示城市的个数和数据类型(其意义将在后面提到)。输入文件的第 2 到 n 行,每行描述一个除SZ之外的城市。其中第 v 行包含 5 个非负整数 f_v,s_v,p_v,q_v,l_v,分别表示城市 v 的父亲城市,它到父亲城市道路的长度,票价的两个参数和距离限制。请注意:输入不包含编号为 1 的SZ市,第 2 行到第 n 行分别描述的是城市 2 到城市 n。
输出
输出包含 n-1 行,每行包含一个整数。其中第 v 行表示从城市 v+1 出发,到达SZ市最少的购票费用。同样请注意:输出不包含编号为 1 的SZ市。
输入示例1
7 3 1 2 20 0 3 1 5 10 100 5 2 4 10 10 10 2 9 1 100 10 3 5 20 100 10 4 4 20 0 10
输出示例1
40 150 70 149 300 150
输入示例2
传送门(点击下载)
输出示例2
数据规模及约定
对于所有测试数据,保证 0≤pv≤106,0≤qv≤1012,1≤fv<v;保证 0<sv≤lv≤2×1011,且任意城市到SZ市的总路程长度不超过 2×1011。
输入的 t 表示数据类型,0≤t<4,其中:
当 t=0 或 2 时,对输入的所有城市 v,都有 fv=v-1,即所有城市构成一个以SZ市为终点的链;
当 t=0 或 1 时,对输入的所有城市 v,都有 lv=2×1011,即没有移动的距离限制,每个城市都能到达它的所有祖先;
当 t=3 时,数据没有特殊性质。
n=2×10^5
题解
借着此题学了学有根树分治
首先不难想出一个dp,设f(i)表示节点 i 到节点 1 所需的最小花费,有 f(i) = min{ f(j) + d(i ~ j) * p(i) + q(i) | j 为 i 祖先 & d(i ~ j) <= l(i) },其中d(i ~ j)表示节点 i 到 j 的距离,p, q, l 的意义见题目描述。
还是基本的思想,把d(i ~ j)拆开,变成dep(i) - dep(j)(dep(i)表示节点 i 到根的距离),于是上述式子转化成 f(i) - dep[i] * p(i) - q(i) = min{ f(j) + p(i) * dep(j) | 条件略 },可以用分治把它转化成一个序列问题,有一定顺序后就可以维护下凸壳了。
分治的思想是:对于子树u,找到其重心rt,分治处理u所在的以rt为根的子树,然后用u到rt这一条链上的信息更新以rt为根的其他子树上节点的信息。
#include <iostream> #include <cstdio> #include <algorithm> #include <cmath> #include <stack> #include <vector> #include <queue> #include <cstring> #include <string> #include <map> #include <set> using namespace std; #define LL long long #define LD double const int BufferSize = 1 << 16; char buffer[BufferSize], *Head, *tail; inline char Getchar() { if(Head == tail) { int l = fread(buffer, 1, BufferSize, stdin); tail = (Head = buffer) + l; } return *Head++; } LL read() { LL x = 0, f = 1; char c = Getchar(); while(!isdigit(c)){ if(c == '-') f = -1; c = Getchar(); } while(isdigit(c)){ x = x * 10 + c - '0'; c = Getchar(); } return x * f; } #define maxn 200010 #define maxm 400010 #define oo 1ll << 60 int n, m, head[maxn], next[maxm], to[maxm], fa[maxn]; LL dep[maxn], f[maxn], p[maxn], q[maxn], l[maxn]; void AddEdge(int a, int b) { to[++m] = b; next[m] = head[a]; head[a] = m; swap(a, b); to[++m] = b; next[m] = head[a]; head[a] = m; return ; } int root, size, siz[maxn], g[maxn]; bool vis[maxn]; void getroot(int u, int pa) { siz[u] = 1; int maxs = 0; for(int e = head[u]; e; e = next[e]) if(!vis[to[e]] && to[e] != pa) { getroot(to[e], u); siz[u] += siz[to[e]]; maxs = max(maxs, siz[to[e]]); } g[u] = max(maxs, size - siz[u]); if(g[u] < g[root]) root = u; return ; } int A[maxn], cnt; void dfs(int u, int pa) { A[++cnt] = u; for(int e = head[u]; e; e = next[e]) if(!vis[to[e]] && to[e] != pa) dfs(to[e], u); return ; } bool cmp(int i, int j) { return dep[i] - l[i] > dep[j] - l[j]; } LD slop(int i, int j) { return (LD)(f[i] - f[j]) / (dep[i] - dep[j]); } int Q[maxn]; void solve(int u) { g[root = 0] = n+1; getroot(u, 0); int ni = root, rt = root; vis[root] = 1; if(!vis[u]) size = siz[u] - siz[rt], solve(u); // printf("%d %d\n", u, ni); cnt = 0; for(int e = head[ni]; e; e = next[e]) if(!vis[to[e]]) dfs(to[e], ni); sort(A + 1, A + cnt + 1, cmp); for(int i = fa[ni]; i != fa[u] && dep[i] >= dep[ni] - l[ni]; i = fa[i]) f[ni] = min(f[ni], f[i] + (dep[ni] - dep[i]) * p[ni] + q[ni]); for(int i = 1, r = 0; i <= cnt; i++) { for(; ni != fa[u] && dep[ni] >= dep[A[i]] - l[A[i]]; ni = fa[ni]) { while(r > 1 && slop(Q[r-1], Q[r]) <= slop(Q[r], ni)) r--; Q[++r] = ni; } int L = 1, R = r + 1; while(R - L > 1) { int M = L + R >> 1; if(M == 1 || slop(Q[M-1], Q[M]) >= (LD)p[A[i]]) L = M; else R = M; } if(L < R) f[A[i]] = min(f[A[i]], f[Q[L]] + (dep[A[i]] - dep[Q[L]]) * p[A[i]] + q[A[i]]); } for(int e = head[rt]; e; e = next[e]) if(!vis[to[e]]) { size = siz[to[e]]; solve(to[e]); } return ; } int main() { // freopen("ex_ticket2.in", "r", stdin); // freopen("out.out", "w", stdout); n = read(); read(); f[1] = 0; for(int i = 2; i <= n; i++) { f[i] = oo; fa[i] = read(); AddEdge(fa[i], i); dep[i] = dep[fa[i]] + read(); p[i] = read(); q[i] = read(); l[i] = read(); } size = n; solve(1); for(int i = 2; i <= n; i++) printf("%lld\n", f[i]); return 0; }