【loj6145】「2017 山东三轮集训 Day7」Easy 动态点分治+线段树
给你一棵 n 个点的树,边有边权。m 次询问,每次给出 l 、r 、x ,求 Minri=ldis(i,x) 。
n,m≤105 。
题解
动态点分治+线段树
分块做法太傻逼了我们把它丢到垃圾桶里。树上距离考虑动态点分治。
求出这棵树的点分树,对每一棵点分树子树开一棵动态开点编号线段树,维护编号在某区间内的点到当前点距离的最大值。
对于一次查询,我们在点分树从 x 到根的路径上所有点对应的线段树上查询 [l,r] 的最大值,dis(i,x)+query(l,r,rooti) 的最大值极为答案。
这样做的正确性比较显然:
1. 每个 [l,r] 内的点都属于这些子树的一个部分内,都被正确统计了一次。
2. 多余统计时,距离只会统计大,不会统计小,没有影响。
时间复杂度 O(nlog2n)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | #include <cstdio> #include <cstring> #include <algorithm> #define N 100010 #define inf 1 << 30 using namespace std; int head[N] , to[N << 1] , len[N << 1] , next[N << 1] , cnt , deep[N] , pos[N] , md[N << 1][20] , log [N << 1] , tot , si[N] , ms[N] , sum , root , vis[N] , fa[N]; int ls[N * 300] , rs[N * 300] , mn[N * 300] , rt[N] , tp; inline void add( int x , int y , int z) { to[++cnt] = y , len[cnt] = z , next[cnt] = head[x] , head[x] = cnt; } void dfs( int x , int pre) { int i; md[++tot][0] = deep[x] , pos[x] = tot; for (i = head[x] ; i ; i = next[i]) if (to[i] != pre) deep[to[i]] = deep[x] + len[i] , dfs(to[i] , x) , md[++tot][0] = deep[x]; } inline int dis( int x , int y) { int t = deep[x] + deep[y] , k; x = pos[x] , y = pos[y]; if (x > y) swap(x , y); k = log [y - x + 1]; return t - 2 * min(md[x][k] , md[y - (1 << k) + 1][k]); } void getroot( int x , int pre) { int i; si[x] = 1 , ms[x] = 0; for (i = head[x] ; i ; i = next[i]) if (!vis[to[i]] && to[i] != pre) getroot(to[i] , x) , si[x] += si[to[i]] , ms[x] = max(ms[x] , si[to[i]]); ms[x] = max(ms[x] , sum - si[x]); if (ms[x] < ms[root]) root = x; } void solve( int x) { int i; vis[x] = 1; for (i = head[x] ; i ; i = next[i]) if (!vis[to[i]]) sum = si[to[i]] , root = 0 , getroot(to[i] , 0) , fa[root] = x , solve(root); } void update( int p , int a , int l , int r , int &x) { if (!x) x = ++tp , mn[x] = inf; mn[x] = min(mn[x] , a); if (l == r) return ; int mid = (l + r) >> 1; if (p <= mid) update(p , a , l , mid , ls[x]); else update(p , a , mid + 1 , r , rs[x]); } int query( int b , int e , int l , int r , int x) { if (!x) return inf; if (b <= l && r <= e) return mn[x]; int mid = (l + r) >> 1 , ans = inf; if (b <= mid) ans = min(ans , query(b , e , l , mid , ls[x])); if (e > mid) ans = min(ans , query(b , e , mid + 1 , r , rs[x])); return ans; } int main() { int n , m , i , j , x , y , z , ans; scanf ( "%d" , &n); for (i = 1 ; i < n ; i ++ ) scanf ( "%d%d%d" , &x , &y , &z) , add(x , y , z) , add(y , x , z); dfs(1 , 0); for (i = 2 ; i <= tot ; i ++ ) log [i] = log [i >> 1] + 1; for (i = 1 ; i <= log [tot] ; i ++ ) for (j = 1 ; j <= tot - (1 << i) + 1 ; j ++ ) md[j][i] = min(md[j][i - 1] , md[j + (1 << (i - 1))][i - 1]); ms[0] = sum = n , root = 0 , getroot(1 , 0) , solve(root); for (i = 1 ; i <= n ; i ++ ) for (j = i ; j ; j = fa[j]) update(i , dis(i , j) , 1 , n , rt[j]); scanf ( "%d" , &m); while (m -- ) { scanf ( "%d%d%d" , &x , &y , &z) , ans = inf; for (i = z ; i ; i = fa[i]) ans = min(ans , dis(i , z) + query(x , y , 1 , n , rt[i])); printf ( "%d\n" , ans); } return 0; } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步