从lca到树链剖分 bestcoder round#45 1003

bestcoder round#45 1003 题,给定两个点,要我们求这两个点的树上路径所经过的点的权值是否出现过奇数次。
如果是一般人,那么就是用lca求树上路径,然后判断是否出现过奇数次(用异或),高手就不这么做了,直接树链剖分。
为什么不能用lca,因为如果有树退化成链,那么每次询问的复杂度是O(n), 那么q次询问的时间复杂度是O(qn)

什么是树链剖分呢? 就是把树的边分成轻链和重链

http://blogsina.com.cn/s/blog_6974c8b20100zc61.html
http://wwwcnblogs.com/BLADEVIL/p/3479713.html
这两个博客写的很好了

剖分后的树有如下性质:
1、如果(u,v)为轻边, 那么 size[v]*2<size[u]
2、从根到某一点的路径上的轻链,重链的个数不大于logn
所以我们可以用线段树来维护这些链,这样如果 要得到任意两点树上路径的信息,那么只要访问线段树,那么时间复杂度只要4logn*logn

第一次dfs进行树链剖分,求出了轻链和重链
第二次dfs对树的结点进行了重新编号(结点的编号就是在线段树中区间中的一点), 重链上的结点的编号是连续的
那么重链上的点在线段树中的区间是连续的。

那么要询问树上任意两点路径权值的最大值, 只要上depth大的那个点往上走, 如果往上走的时候,遇到的重链,那么可以进去区间查询,然后一次
走过重链即可。

对于bestcoder round#45 1003 题,
第二次dfs求出编号后,建线段树,然后区间的值等于子区间的异或
那么任意两点的树上路径的权值是否重复,只要访问线段树区间,看最后的异或的值是为0

  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <stdlib.h>
  4 #include <algorithm>
  5 #include <iostream>
  6 #include <queue>
  7 #include <stack>
  8 #include <vector>
  9 #include <map>
 10 #include <set>
 11 #include <string>
 12 #include <math.h>
 13 using namespace std;
 14 #pragma comment(linker, "/STACK:1024000000,1024000000")
 15 #pragma warning(disable:4996)
 16 typedef long long LL;
 17 const int INF = 1<<30;
 18 void input(int &x)
 19 {
 20     char ch = getchar();
 21     while (ch<‘0‘ || ch>‘9‘)
 22         ch = getchar();
 23     x = 0;
 24     while (ch >= ‘0‘&&ch <= ‘9‘)
 25     {
 26         x = x * 10 + ch - ‘0‘;
 27         ch = getchar();
 28     }
 29 }
 30 /*
 31 第一次dfs进行树链剖分,求出了轻链和重链
 32 第二次dfs对树的结点进行了重新编号, 重链上的结点的编号是连续的
 33 那么重链上的点在线段树中的区间是连续的
 34 剖分后的树有如下性质:
 35 性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];
 36 性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。
 37
 38 那么要询问树上任意两点路径权值的最大值, 只要上depth大的那个点往上走, 如果往上走的时候,遇到的重链,那么可以进去区间查询,然后一次
 39 走过重链
 40 根据性质2:每次询问的复杂度是O(logn*logn)
 41 */
 42 const int N = 100000 + 10;
 43 int size[N], depth[N], son[N], fa[N], w[N], top[N], ra[N], num;
 44 int value[N];
 45 int tree[N * 4];
 46 vector<int> g[N];
 47 int ans;
 48 void dfs(int u)
 49 {
 50     size[u] = 1;
 51     son[u] = 0;
 52     for (int i = 0; i < g[u].size(); ++i)
 53     {
 54         int v = g[u][i];
 55         if (v != fa[u])
 56         {
 57             depth[v] = depth[u] + 1;
 58             fa[v] = u;
 59             dfs(v);
 60             size[u] += size[v];
 61             if (size[v]>size[son[u]])
 62                 son[u] = v;
 63         }
 64     }
 65 }
 66 void dfs2(int u, int tp)
 67 {
 68     top[u] = tp;
 69     w[u] = ++num;//对树上的结点进行编号
 70     ra[num] = u;
 71     //因为优先dfs重儿子,所以一条重链上的点的编号是连续的
 72     if (son[u] != 0)
 73         dfs2(son[u], top[u]);
 74     for (int i = 0; i < g[u].size(); ++i)
 75     {
 76         int v = g[u][i];
 77         if (v != son[u] && v != fa[u])
 78             dfs2(v, v);
 79     }
 80 }
 81 void pushUp(int rt)
 82 {
 83     tree[rt] = tree[rt << 1] ^ tree[rt << 1 | 1];
 84 }
 85 void build(int l, int r, int rt)
 86 {
 87     if (l == r)
 88     {
 89         tree[rt] = value[ra[l]];//ra[l] 是编号为l的是哪个结点
 90         return;
 91     }
 92     int mid = (l + r) >> 1;
 93     build(l, mid, rt << 1);
 94     build(mid + 1, r, rt << 1 | 1);
 95     pushUp(rt);
 96 }
 97 //修改某个结点的值, 那么要将父区间异或旧的值(即去除原先的值),然后异或新的值
 98 void update(int l, int r, int rt, int pos, int newVal, int oldVal)
 99 {
100     tree[rt] = tree[rt] ^ oldVal^newVal;
101     if (l == r)
102         return;
103     int mid = (l + r) >> 1;
104     if (pos <= mid)
105         update(l, mid, rt << 1, pos, newVal, oldVal);
106     else
107         update(mid + 1, r, rt << 1 | 1, pos, newVal, oldVal);
108 }
109 void query(int l, int r, int rt, int L, int R)
110 {
111     if (L <= l && R >= r)
112     {
113         ans ^= tree[rt];
114         return;
115     }
116     int mid = (l + r) >> 1;
117     if (L <= mid)
118         query(l, mid, rt << 1, L, R);
119     if (R > mid)
120         query(mid + 1, r, rt << 1 | 1, L, R);
121 }
122 int main()
123 {
124     int t, n, q, op,a, b;
125     scanf("%d", &t);
126     while (t--)
127     {
128         num = 0;
129         scanf("%d%d", &n, &q);
130         for (int i = 1; i <= n; ++i)
131             g[i].clear();
132         for (int i = 1; i < n; ++i)
133         {
134             scanf("%d%d", &a, &b);
135             g[a].push_back(b);
136             g[b].push_back(a);
137         }
138         for (int i = 1; i <= n; ++i)
139         {
140             //scanf("%d", &value[i]);
141             input(value[i]);
142             value[i]++;
143         }
144         depth[1] = fa[1] = 0;
145         dfs(1);
146         dfs2(1, 1);
147         build(1, n, 1);
148         while (q--)
149         {
150             scanf("%d%d%d", &op, &a, &b);
151
152             if (op == 0)
153             {
154                 b++;
155                 update(1, n, 1, w[a], b, value[a]);
156                 value[a] = b;
157             }
158             else
159             {
160                 ans = 0;
161                 //不停的往上走,像lca一样,不过比lca更优,因为有重链的存在,可以一次走很多部
162                 while (top[a] != top[b])
163                 {
164                     if (depth[top[a]] < depth[top[b]])
165                         swap(a, b);
166                     query(1, n, 1, w[top[a]], w[a]);
167                     a = fa[top[a]];
168                 }
169                 if (depth[a] > depth[b])
170                     swap(a, b);
171                 query(1, n, 1, w[a], w[b]);
172                 if (ans == 0)
173                     printf("%d\n", -1);
174                 else
175                     printf("%d\n", ans - 1);
176             }
177         }
178     }
179     return 0;
180 }

时间: 2024-10-25 12:09:47

从lca到树链剖分 bestcoder round#45 1003的相关文章

LCA 倍增||树链剖分

方法1:倍增 1498ms #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <cmath> using namespace std; typedef long long ll; const int N=5e5+5; inline int read(){ char c=getchar();int x=0,f=1; while

BZOJ 3626 LCA(离线+树链剖分)

首先注意到这样一个事实. 树上两个点(u,v)的LCA的深度,可以转化为先将u到根路径点权都加1,然后求v到根路径上的总点权值. 并且该题支持离线.那么我们可以把一个区间询问拆成两个前缀和形式的询问. 现在问题就变成了求[1,r]和x的LCA深度之和.实际上就是把[1,r]到根路径点权点1,然后求x到根路径上的总权值. 我们按编号从小往大依次加路径点权.然后就可以有序处理询问.用树链剖分维护的话,总复杂度为O((n+q)lognlogn).

CF 191C Fools and Roads lca 或者 树链剖分

They say that Berland has exactly two problems, fools and roads. Besides, Berland has n cities, populated by the fools and connected by the roads. All Berland roads are bidirectional. As there are many fools in Berland, between each pair of cities th

洛谷P4180 [Beijing2010组队]次小生成树Tree(最小生成树,LCT,主席树,倍增LCA,倍增,树链剖分)

洛谷题目传送门 %%%天平巨佬和山楠巨佬%%% 他们的题解 思路分析 具体思路都在两位巨佬的题解中.这题做法挺多的,我就不对每个都详细讲了,泛泛而谈吧. 首先kruskal把最小生成树弄出来,因为要求次小生成树.至于为什么次小一定只在最小的基础上改变了一条边,我也不会证......打表找规律大法好 剩下的可以有一堆数据结构来维护最大值和次大值(原理两位巨佬都讲清楚了,这里只分析一下算法的优劣) 倍增+LCA 山楠巨佬的做法,我也写了这一种.复杂度\(O(MlogM(kruscal)+MlogN(

bzoj 3626: [LNOI2014]LCA 离线+树链剖分

3626: [LNOI2014]LCA Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 426  Solved: 124[Submit][Status] Description 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1.设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先.有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[LCA(i,z)].(即

[BZOJ 3626] [LNOI2014] LCA 【树链剖分 + 离线 + 差分询问】

题目链接: BZOJ - 3626 题目分析 考虑这样的等价问题,如果我们把一个点 x 到 Root 的路径上每个点的权值赋为 1 ,其余点的权值为 0,那么从 LCA(x, y) 的 Depth 就是从 y 到 Root 的路径上的点权和. 这个方法是可以叠加的,这是非常有用的一点.如果我们把 [l, r] 的每个点到 Root 的路径上所有点的权值 +1,再求出从 c 到 Root 的路径点权和,即为 [l, r] 中所有点与 c 的 LCA 的 Depth 和. 不仅满足可加性,还满足可减

【BZOJ3626】[LNOI2014]LCA 离线+树链剖分+线段树

[BZOJ3626][LNOI2014]LCA Description 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1.设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先.有q次询问,每次询问给出l r z,求sigma_{l<=i<=r}dep[LCA(i,z)].(即,求在[l,r]区间内的每个节点i与z的最近公共祖先的深度之和) Input 第一行2个整数n q.接下来n-1行,分别表示点1到点n-1的父节点编号.接

bzoj3626: [LNOI2014]LCA (树链剖分)

很神奇的方法 感觉是有生之年都想不到正解的这种 考虑对i 到根的节点权值 + 1,则从根到z的路径和就是lca(i,z)的深度 所以依次把0 ~ n - 1的点权值 + 1 对于询问[l, r] 这个区间关于z 的深度和,就用(1, r) - (1, l - 1)的值表示 详见黄学长的博客啦 http://hzwer.com/3415.html 下面给出代码 #include <cstdio> #include <vector> #include <algorithm>

bzoj3626: [LNOI2014]LCA 离线+树链剖分

理解了半天. 题解:http://hzwer.com/3891.html #include<bits/stdc++.h> #define N 50010 #define M (l+r>>1) #define P (k<<1) #define S (k<<1|1) #define K l,r,k #define L l,M,P #define R M+1,r,S #define Z int l=1,int r=n,int k=1 using namespace