1. 程式人生 > 實用技巧 >JZOI 5248. 花花的聚會

JZOI 5248. 花花的聚會

洛谷AC通道!

首先,有一個推論,如果我們要保證能到達首都,那麼朋友們的起點一定有車票,不然寸步難行啊!

所以,我們不用管那些沒有車票的點了,直接考慮有車票的點。只有他們才可能作為朋友們的起點。

考慮DP。設 $f_u = min(f_v) + cost_u $. 其中,v 為 u 的祖先,$cost_u$即為u這個點的車票價額,其車票也必須保證可以從$u$到$v$(直接用深度算)。

不難想到,我們的DP必須要從上往下更新,因此要對車票進行排序。同時,我們需要找到其祖先的最小值,即$min(f_v)$,如果暴力列舉,肯定會T掉一部分。

考慮用資料結構來維護區間最小值。

於是,樹剖+線段樹橫空出世:

用樹剖獲取每個點的$dfn$, 將車票按照$dfn$排序,然後建立關於$dp$陣列的線段樹,按照$dfn$維護區間最小值,同時快速查詢。

於是,

此題AC。

#include <bits/stdc++.h>
using namespace std;
#define N 100001
#define ll long long

/*   dp[i] = min(dp[祖先] + cost[j])  
    cost 的車票應滿足條件 
 */

inline int read(){
    int x = 0, s = 1;
    char c = getchar();
    while
(!isdigit(c)){ if(c == '-') s = -1; c = getchar(); } while(isdigit(c)){ x = x * 10 + (c ^ '0'); c = getchar(); } return x * s; } struct node{ int v, next; } t[N << 1]; int f[N]; int n, m; ll dp[N]; int bian = 0; inline void add(int u, int v){ t[
++bian] = (node){v, f[u]}, f[u] = bian; return ; } struct piao{ int x, k, cost; inline void insert(){ x = read(), k = read(), cost = read(); return ; } } g[N]; /*--------樹剖begin--------*/ int top[N], fa[N], son[N], siz[N], deth[N]; int dfn[N], rev[N], id = 0; #define v t[i].v void dfs1(int now, int father){ fa[now] = father; siz[now] = 1; deth[now] = deth[father] + 1; for(int i = f[now]; i; i = t[i].next){ if(v != father){ dfs1(v, now); siz[now] += siz[v]; if(siz[v] > siz[son[now]]) son[now] = v; } } return ; } void dfs2(int now, int tp){ top[now] = tp; dfn[now] = ++id; rev[id] = now; if(!son[now]) return ; dfs2(son[now], tp); for(int i = f[now]; i; i = t[i].next){ if(v != fa[now] && v != son[now]) dfs2(v, v); } return ; } #undef v /*------end-------*/ /*------- segment tree ------*/ struct tree{ // 按照 dfn的順序維護 dp 最小值 ll minn; } e[N << 2]; inline void pushup(int o){ e[o].minn = min(e[o << 1].minn, e[o << 1 | 1].minn); return ; } void build(int o, int l, int r){ if(l == r){ e[o].minn = (ll)1e18; return ; } int mid = l + r >> 1; build(o << 1, l, mid); build(o << 1 | 1, mid + 1, r); pushup(o); return ; } void update(int o, int l, int r, int x, ll k){ if(l > x || r < x) return ; if(l == r && l == x){ e[o].minn = min(e[o].minn, (ll)k); return ; } int mid = l + r >> 1; update(o << 1, l, mid, x, k); update(o << 1 | 1, mid + 1, r, x, k); pushup(o); return ; } ll query(int o, int l, int r, int in, int end){ if(l > end || r < in){ return (ll)1e18; } if(l >= in && r <= end){ return e[o].minn; } int mid = l + r >> 1; return min(query(o << 1, l, mid, in, end), query(o << 1 | 1, mid + 1, r, in, end)); } ll ask_min(int x, int len){ ll now = x, temp = (ll)1e18; while(deth[x] - deth[top[now]] <= len && now){ // 注意排除根 temp = min(temp, query(1, 1, n, dfn[top[now]], dfn[now])); // 查詢整條鏈上最小值 now = fa[top[now]]; } if(now == 1 || deth[x] - deth[now] > len) return temp; /*注意重鏈等特殊情況*/ ll l = dfn[top[now]], r = dfn[now], pos = r; ll mid = l + r >> 1; while(l <= r){ // 二分找該鏈上可到達位置 int mid = l + r >> 1; if(deth[x] - deth[rev[mid]] <= len) pos = mid, r = mid - 1; else l = mid + 1; } temp = min(temp, query(1, 1, n, pos, dfn[now])); return temp; } bool cmp(piao a, piao b){ // 按照 dfn 排序 return dfn[a.x] < dfn[b.x]; } int main(){ // freopen("hh.txt", "r", stdin); n = read(), m = read(); for(int i = 1; i < n; i++){ int x = read(), y = read(); add(x, y); add(y, x); // 加成多向邊方便跑圖 } dfs1(1, 0); //fa[1]必須設為0,防止影響到第二層的兒子 dfs2(1, 1); for(int i = 1;i <= m; i++) g[i].insert(); sort(g + 1, g + m + 1, cmp); // 按照 dfn 排序, 自上而下更新 dp memset(dp,37,sizeof(dp)); dp[1] = 0; build(1, 1, n); update(1, 1, n, 1, 0); for(int i = 1;i <= m; i++){ int x = g[i].x, k = g[i].k, cost = g[i].cost; if(x == 1) continue; // 排除根 ll temp = ask_min(x, k) + cost; if(temp < dp[x]){ dp[x] = min(dp[x], temp); // 起點必定有車票 update(1, 1, n, dfn[x], dp[x]); } } int Q = read(); while(Q--){ int x = read(); printf("%lld\n", dp[x]); } return 0; }