【bzoj4719】[Noip2016]天天爱跑步 权值线段树合并
题目描述
给出一棵n个点的树,以及m次操作,每次操作从起点向终点以每秒一条边的速度移动(初始时刻为0),最后对于每个点询问有多少次操作在经过该点的时刻为某值。
输入
第一行有两个整数N和M 。其中N代表树的结点数量, 同时也是观察员的数量, M代表玩家的数量。
接下来n-1 行每行两个整数U和V ,表示结点U 到结点V 有一条边。
接下来一行N 个整数,其中第个整数为Wj , 表示结点出现观察员的时间。
接下来 M行,每行两个整数Si和Ti,表示一个玩家的起点和终点。
对于所有的数据,保证 。
1<=Si,Ti<=N,0<=Wj<=N
输出
输出1行N 个整数,第个整数表示结点的观察员可以观察到多少人。
样例输入
6 3
2 3
1 2
1 4
4 5
4 6
0 2 5 1 2 3
1 5
1 3
2 6
样例输出
2 0 0 1 1 1
题解
自己YY出的权值线段树合并
传说中的Noi没有p2016的神题。。。写出1个log的算法好像也不是很难。。。
先说下我的方法:
把每次操作拆成两部分:x->lca和lca->y,可以发现第一部分经过的点的 时刻+深度 是一个定值,第二部分经过的点的 时刻-深度 也是一个定值。所以问题转化为:给一条链上的每个点加1个定值、询问每个点某值的出现次数。
以第一种操作为例:由于只有1次总询问,因此可以差分打标记来实现修改,即在x处给权值标记+1,fa[lca]处给权值标记-1。然后处理询问时要求的就是子树的所有标记之和。可以使用线段树自底向上合并的方法来实现维护子树的所有标记之和。
总的时间复杂度为$O((n+m)\log n)$。
#include <cstdio> #include <cstring> #include <algorithm> #define N 300010 #define lson l , mid , ls[x] #define rson mid + 1 , r , rs[x] using namespace std; int n , head[N] , to[N << 1] , next[N << 1] , cnt , fa[N][20] , deep[N] , log[N]; int w[N] , ra[N] , rb[N] , ls[N << 6] , rs[N << 6] , si[N << 6] , tot , ans[N]; void update(int p , int a , int l , int r , int &x) { if(!x) x = ++tot; si[x] += a; if(l == r) return; int mid = (l + r) >> 1; if(p <= mid) update(p , a , lson); else update(p , a , rson); } int merge(int x , int y) { if(!x) return y; if(!y) return x; si[x] += si[y]; ls[x] = merge(ls[x] , ls[y]); rs[x] = merge(rs[x] , rs[y]); return x; } int query(int p , int l , int r , int x) { if(!x) return 0; if(l == r) return si[x]; int mid = (l + r) >> 1; if(p <= mid) return query(p , lson); else return query(p , rson); } inline void add(int x , int y) { to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt; } void dfs(int x) { int i; for(i = 1 ; (1 << i) <= deep[x] ; i ++ ) fa[x][i] = fa[fa[x][i - 1]][i - 1]; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa[x][0]) fa[to[i]][0] = x , deep[to[i]] = deep[x] + 1 , dfs(to[i]); } inline int lca(int x , int y) { int i; if(deep[x] < deep[y]) swap(x , y); for(i = log[deep[x] - deep[y]] ; ~i ; i -- ) if(deep[x] - deep[y] >= (1 << i)) x = fa[x][i]; if(x == y) return x; for(i = log[deep[x]] ; ~i ; i -- ) if(deep[x] >= (1 << i) && fa[x][i] != fa[y][i]) x = fa[x][i] , y = fa[y][i]; return fa[x][0]; } void solve(int x) { int i; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa[x][0]) solve(to[i]) , ra[x] = merge(ra[x] , ra[to[i]]) , rb[x] = merge(rb[x] , rb[to[i]]); ans[x] = query(w[x] + deep[x] , 0 , n << 1 , ra[x]) + query(w[x] - deep[x] , -n , n , rb[x]); } int main() { int m , i , x , y , z; scanf("%d%d" , &n , &m); for(i = 2 ; i <= n ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x) , log[i] = log[i >> 1] + 1; dfs(1); for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &w[i]); for(i = 1 ; i <= m ; i ++ ) { scanf("%d%d" , &x , &y) , z = lca(x , y); update(deep[x] , 1 , 0 , n << 1 , ra[x]) , update(deep[x] , -1 , 0 , n << 1 , ra[fa[z][0]]); update(deep[x] - 2 * deep[z] , 1 , -n , n , rb[y]) , update(deep[x] - 2 * deep[z] , -1 , -n , n , rb[z]); } solve(1); for(i = 1 ; i < n ; i ++ ) printf("%d " , ans[i]); printf("%d" , ans[n]); return 0; }
写完发现自己naive了。。。
事实上,求的是子树内的标记和。和是存在逆运算的(差),可以转化为 包含子树的部分与不包含子树的部分的差 来求出。
具体地,在递归子树之前的答案为a,递归子树之后的答案为b,那么b-a就是子树内的答案。
所以可以直接使用递归前后的差作为答案。此时不再需要单独维护子树,所以可以使用桶来维护答案,直接使用两次桶里的值作差即可。这个部分的复杂度降为了$O(n)$。
于是只需要在线性时间内求出每次操作的LCA即可做到线性复杂度。惜哉我不会写Tarjan离线LCA,于是这一部分的代码也没有补上。。。
所以贴一个倍增LCA+桶的代码吧(代码中细节还是不少的,比如桶的下标不能为负等等):
#include <cstdio> #include <vector> #define N 300010 #define lson l , mid , ls[x] #define rson mid + 1 , r , rs[x] using namespace std; vector<int> va[N] , vb[N]; int n , head[N] , to[N << 1] , next[N << 1] , cnt , fa[N][20] , deep[N] , log[N]; int w[N] , ta[N << 1] , tb[N << 1] , ans[N]; inline void add(int x , int y) { to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt; } void dfs(int x) { int i; for(i = 1 ; (1 << i) <= deep[x] ; i ++ ) fa[x][i] = fa[fa[x][i - 1]][i - 1]; for(i = head[x] ; i ; i = next[i]) if(to[i] != fa[x][0]) fa[to[i]][0] = x , deep[to[i]] = deep[x] + 1 , dfs(to[i]); } inline int lca(int x , int y) { int i; if(deep[x] < deep[y]) swap(x , y); for(i = log[deep[x] - deep[y]] ; ~i ; i -- ) if(deep[x] - deep[y] >= (1 << i)) x = fa[x][i]; if(x == y) return x; for(i = log[deep[x]] ; ~i ; i -- ) if(deep[x] >= (1 << i) && fa[x][i] != fa[y][i]) x = fa[x][i] , y = fa[y][i]; return fa[x][0]; } void solve(int x) { int i; ans[x] -= ta[w[x] + deep[x] + 1] + tb[w[x] - deep[x] + n]; for(i = 0 ; i < (int)va[x].size() ; i ++ ) { if(va[x][i] > 0) ta[va[x][i]] ++ ; else ta[-va[x][i]] -- ; } for(i = 0 ; i < (int)vb[x].size() ; i ++ ) { if(vb[x][i] > 0) tb[vb[x][i]] ++ ; else tb[-vb[x][i]] -- ; } for(i = head[x] ; i ; i = next[i]) if(to[i] != fa[x][0]) solve(to[i]); ans[x] += ta[w[x] + deep[x] + 1] + tb[w[x] - deep[x] + n]; } int main() { int m , i , x , y , z; scanf("%d%d" , &n , &m); for(i = 2 ; i <= n ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x) , log[i] = log[i >> 1] + 1; dfs(1); for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &w[i]); for(i = 1 ; i <= m ; i ++ ) { scanf("%d%d" , &x , &y) , z = lca(x , y); va[x].push_back(deep[x] + 1) , va[fa[z][0]].push_back(-deep[x] - 1); vb[y].push_back(n + deep[x] - 2 * deep[z]) , vb[z].push_back(2 * deep[z] - deep[x] - n); } solve(1); for(i = 1 ; i < n ; i ++ ) printf("%d " , ans[i]); printf("%d" , ans[n]); return 0; }