[HNOI 2015]开店

Description

 风见幽香有一个好朋友叫八云紫,她们经常一起看星星看月亮从诗词歌赋谈到

人生哲学。最近她们灵机一动,打算在幻想乡开一家小店来做生意赚点钱。这样的
想法当然非常好啦,但是她们也发现她们面临着一个问题,那就是店开在哪里,面
向什么样的人群。很神奇的是,幻想乡的地图是一个树形结构,幻想乡一共有 n
个地方,编号为 1 到 n,被 n-1 条带权的边连接起来。每个地方都住着一个妖怪,
其中第 i 个地方的妖怪年龄是 x_i。妖怪都是些比较喜欢安静的家伙,所以它们并
不希望和很多妖怪相邻。所以这个树所有顶点的度数都小于或等于 3。妖怪和人一
样,兴趣点随着年龄的变化自然就会变化,比如我们的 18 岁少女幽香和八云紫就
比较喜欢可爱的东西。幽香通过研究发现,基本上妖怪的兴趣只跟年龄有关,所以
幽香打算选择一个地方 u(u为编号),然后在 u开一家面向年龄在 L到R 之间(即
年龄大于等于 L、小于等于 R)的妖怪的店。也有可能 u这个地方离这些妖怪比较
远,于是幽香就想要知道所有年龄在 L 到 R 之间的妖怪,到点 u 的距离的和是多
少(妖怪到 u 的距离是该妖怪所在地方到 u 的路径上的边的权之和) ,幽香把这个
称为这个开店方案的方便值。幽香她们还没有决定要把店开在哪里,八云紫倒是准
备了很多方案,于是幽香想要知道,对于每个方案,方便值是多少呢。

Input

 第一行三个用空格分开的数 n、Q和A,表示树的大小、开店的方案个数和妖

怪的年龄上限。 
第二行n个用空格分开的数 x_1、x_2、…、x_n,x_i 表示第i 个地点妖怪的年
龄,满足0<=x_i<A。(年龄是可以为 0的,例如刚出生的妖怪的年龄为 0。) 
接下来 n-1 行,每行三个用空格分开的数 a、b、c,表示树上的顶点 a 和 b 之
间有一条权为c(1 <= c <= 1000)的边,a和b 是顶点编号。 
接下来Q行,每行三个用空格分开的数 u、 a、 b。对于这 Q行的每一行,用 a、
b、A计算出 L和R,表示询问“在地方 u开店,面向妖怪的年龄区间为[L,R]的方
案的方便值是多少”。对于其中第 1 行,L 和 R 的计算方法为:L=min(a%A,b%A), 
R=max(a%A,b%A)。对于第 2到第 Q行,假设前一行得到的方便值为 ans,那么当
前行的 L 和 R 计算方法为: L=min((a+ans)%A,(b+ans)%A), 
R=max((a+ans)%A,(b+ans)%A)。 

Output

对于每个方案,输出一行表示方便值。 

Sample Input

10 10 10
0 0 7 2 1 4 7 7 7 9
1 2 270
2 3 217
1 4 326
2 5 361
4 6 116
3 7 38
1 8 800
6 9 210
7 10 278
8 9 8
2 8 0
9 3 1
8 0 8
4 2 7
9 7 3
4 7 0
2 2 7
3 2 1
2 3 4

Sample Output

1603
957
7161
9466
3232
5223
1879
1669
1282
0

HINT

 满足 n<=150000,Q<=200000。对于所有数据,满足 A<=10^9

题解

若 $u,v$ 在一棵树上,显然存在 $$dist_{u, v} = dist_{root, u}+dist_{root, v}-2*dist_{root, lca(u, v)}$$

其中 $dist_{u, v}$ 表示树上 $u<->v$ 之间的路径长度。

而对于此题,显然每组询问 $[L, R]$ 的答案,就是(记点权在集合 $[L, R]$ 中的点为点集 $S$ ):

$$ans = sum_{v in S} (dist_{root, u}+dist_{root, v}-2*dist_{root, lca(u, v)})$$
$$= |S|*dist_{root, u}+sum_{v in S}dist_{root, v}-2*sum_{v in S}dist_{root, lca(u, v)}$$

注意到 $|S|*dist_{root, u}$ 可以直接求;我们按每个点的点权排序,显然 $sum_{v in S}dist_{root, v}$ 是可以用前缀和预处理出来的。

现在需要解决的问题就是如何求 $sum_{v in S}dist_{root, lca(u, v)}$ 。

我们注意到这样一个事情:对于求 $dist_{root, lca(u, v)}$ ,我们可以先将 $root<->u$ 的路径上的边标记;再询问 $root<->v$ 的边,询问到的有标记边的就是 $root<->lca(u, v)$ 上的边。

对于这个方法,我们可以将按点权排序后的点,按序存到主席树中。询问 $[L, R]$ 就是询问 $[1, R]-[1, L-1]$ 。

实现方面,值得注意的是,对于新建的主席树,因为树剖每次会修改可能不止一个区间,所以在这个版本下新建的节点可以修改,我们记录一个数组 $pre_i$ 表示第 $i$ 个版本(及之前)动态开点开的最大的值。新建节点时,若当前节点 $o > pre$ 即 $o$ 是当前版本才新建的,所以可以直接修改。

询问的时候,若有 $lazy$ 标记,我是直接暴力下放的,不知道有没有更好的方法。

  1 //It is made by Awson on 2018.1.1
  2 #include <set>
  3 #include <map>
  4 #include <cmath>
  5 #include <ctime>
  6 #include <queue>
  7 #include <stack>
  8 #include <cstdio>
  9 #include <string>
 10 #include <vector>
 11 #include <cstdlib>
 12 #include <cstring>
 13 #include <iostream>
 14 #include <algorithm>
 15 #define LL long long
 16 #define Max(a, b) ((a) > (b) ? (a) : (b))
 17 #define Min(a, b) ((a) < (b) ? (a) : (b))
 18 using namespace std;
 19 const int N = 150000;
 20 const int M = N*40;
 21 const int INF = ~0u>>1;
 22 
 23 int n, q, a, u, v, c, x1, x2;
 24 struct tt {
 25     int to, next, cost;
 26 }edge[(N<<1)+5];
 27 int path[N+5], tot;
 28 struct value {
 29     int x, id;
 30     bool operator < (const value &b) const {
 31     return x < b.x;
 32     }
 33 }w[N+5];
 34 int size[N+5], son[N+5], sonc[N+5], fa[N+5], dep[N+5], pos[N+5], top[N+5], cnt;
 35 LL val[N+5], dist[N+5], sumdist[N+5];
 36 struct Segment_tree {
 37     int root[N+5], pre[N+5], lazy[M+5], ch[M+5][2], pos;
 38     LL sum[M+5];
 39     void pushdown(int o, int l, int mid, int r, int last) {
 40         if (ch[o][0] <= last) {
 41         int ls = ch[o][0]; ch[o][0] = ++pos;
 42         lazy[ch[o][0]] = lazy[ls], ch[ch[o][0]][0] = ch[ls][0], ch[ch[o][0]][1] = ch[ls][1], sum[ch[o][0]] = sum[ls];
 43     }
 44     if (ch[o][1] <= last) {
 45         int rs = ch[o][1]; ch[o][1] = ++pos;
 46         lazy[ch[o][1]] = lazy[rs], ch[ch[o][1]][0] = ch[rs][0], ch[ch[o][1]][1] = ch[rs][1], sum[ch[o][1]] = sum[rs];
 47     }
 48         sum[ch[o][0]] += (val[mid]-val[l-1])*lazy[o], sum[ch[o][1]] += (val[r]-val[mid])*lazy[o];
 49         lazy[ch[o][0]] += lazy[o], lazy[ch[o][1]] += lazy[o], lazy[o] = 0;
 50     }
 51     void update(int &o, int l, int r, int a, int b, int last) {
 52     if (o <= last) {
 53         int rt = o; o = ++pos;
 54         lazy[o] = lazy[rt], ch[o][0] = ch[rt][0], ch[o][1] = ch[rt][1], sum[o] = sum[rt];
 55     }
 56         if (a <= l && r <= b) {
 57             lazy[o] += 1, sum[o] += val[r]-val[l-1];
 58         return;
 59         }
 60         int mid = (l+r)>>1;
 61         if (lazy[o]) pushdown(o, l, mid, r, last);
 62         if (mid >= a) update(ch[o][0], l, mid, a, b, last);
 63         if (mid < b) update(ch[o][1], mid+1, r, a, b, last);
 64     sum[o] = sum[ch[o][0]]+sum[ch[o][1]];
 65     }
 66     LL query(int o, int l, int r, int a, int b) {
 67     if (a <= l && r <= b) return sum[o];
 68     int mid = (l+r)>>1; LL c1 = 0, c2 = 0;
 69     if (lazy[o]) pushdown(o, l, mid, r, INF);
 70     if (mid >= a) c1 = query(ch[o][0], l, mid, a, b);
 71     if (mid < b) c2 = query(ch[o][1], mid+1, r, a, b);
 72     return c1+c2;
 73     }
 74 }T;
 75 
 76 void add(int u, int v, int c) {
 77     edge[++tot].to = v;
 78     edge[tot].cost = c;
 79     edge[tot].next = path[u];
 80     path[u] = tot;
 81 }
 82 void dfs1(int u, int father, int depth) {
 83     size[u] = 1, fa[u] = father, dep[u] = depth;
 84     for (int i = path[u]; i; i = edge[i].next)
 85     if (edge[i].to != father) {
 86         dfs1(edge[i].to, u, depth+1);
 87         size[u] += size[edge[i].to];
 88         if (size[edge[i].to] > size[son[u]]) son[u] = edge[i].to, sonc[u] = edge[i].cost;
 89     }
 90 }
 91 void dfs2(int u, int tp, int cost) {
 92     top[u] = tp, pos[u] = ++cnt, val[cnt] = cost;
 93     if (son[u]) {
 94     dist[son[u]] = dist[u]+sonc[u];
 95     dfs2(son[u], tp, sonc[u]);
 96     }
 97     for (int i = path[u]; i; i = edge[i].next)
 98     if (edge[i].to != fa[u] && edge[i].to != son[u]) {
 99         dist[edge[i].to] = dist[u]+edge[i].cost;
100         dfs2(edge[i].to, edge[i].to, edge[i].cost);
101     }
102 }
103 void lca_update(int &o, int x, int last) {
104     while (x) {
105     T.update(o, 1, n, pos[top[x]], pos[x], last);
106     x = fa[top[x]];
107     }
108 }
109 LL lca_query(int o, int x) {
110     LL ans = 0;
111     while (x) {
112     ans += T.query(o, 1, n, pos[top[x]], pos[x]);
113     x = fa[top[x]];
114     }
115     return ans;
116 }
117 int lowerbound(int x) {
118     int L = 1, R = n, ans = 1;
119     while (L <= R) {
120     int mid = (L+R)>>1;
121     if (w[mid].x < x) L = mid+1;
122     else R = mid-1, ans = mid;
123     }
124     return ans;
125 }
126 int upperbound(int x) {
127     int L = 1, R = n, ans = 1;
128     while (L <= R) {
129     int mid = (L+R)>>1;
130     if (w[mid].x > x) R = mid-1;
131     else L = mid+1, ans = mid;
132     }
133     return ans;
134 }
135 void work() {
136     scanf("%d%d%d", &n, &q, &a);
137     for (int i = 1; i <= n; i++) scanf("%d", &w[i].x), w[i].id = i;
138     sort(w+1, w+1+n);
139     for (int i = 1; i < n; i++) {
140     scanf("%d%d%d", &u, &v, &c);
141     add(u, v, c); add(v, u, c);
142     }
143     dfs1(1, 0, 1), dfs2(1, 1, 0);
144     for (int i = 1; i <= n; i++) val[i] += val[i-1];
145     for (int i = 1; i <= n; i++) {
146     T.root[i] = T.root[i-1];
147     lca_update(T.root[i], w[i].id, T.pre[i-1]);
148     T.pre[i] = T.pos, sumdist[i] = sumdist[i-1]+dist[w[i].id];
149     }
150     LL ans = 0;
151     while (q--) {
152     scanf("%d%d%d", &u, &x1, &x2);
153     x1 = (x1+ans)%a, x2 = (x2+ans)%a;
154     if (x1 > x2) swap(x1, x2);
155     x1 = lowerbound(x1), x2 = upperbound(x2);
156     LL a1 = lca_query(T.root[x1-1], u), a2 = lca_query(T.root[x2], u);
157     ans = (x2-x1+1)*dist[u]+sumdist[x2]-sumdist[x1-1]-(a2-a1)*2;
158     printf("%lld
", ans);
159     }
160 }
161 int main() {
162     work();
163     return 0;
164 }
主席树

upd 18.1.8:拿动态点分玩♂下这道题。

网上题解看不懂,自己 YY 了一个做 Fa♂。似乎和题解是一样的...

首先建成点分树后,我们用一个数据结构来维护一下所有点到重心的距离,记点分树上 $o$ 的父亲为 $fa_o$ ,并维护所有点到 $fa_o$ 的距离。

我们可以用 $vector$ 来存下这些数据。对于查询 $[L, R]$ 我们可以二分 $vector$ 。所以我们先不讨论范围问题。

查询时,我们发现,在点分树上爬的时候,我们需要统计除了来的子树外的其他子树中所有节点到当前重心的距离,并且加上这一部分通过重心到询问的点的距离。

如何求这一部分通过重心到询问的点的距离?我们可以在处理上个重心的时候预先减去 $size_x*dist(o, fa_x)$ ,再在处理这个重心时加上 $size_x*dist(x, o)$ 。注意 $x$ 是当前处理的重心,即上述两式中 $x$ 的含义不一样。

如何统计除了来的子树外的其他子树中所有节点到当前重心的距离?也用上面的思想,我们可以先减去以上一个重心为根的子树中所有点到当前处理的的重心的距离和,然后再直接加上以当前处理的重心为根的子树中所有的点到重心的距离。

总时间复杂度就是 $O((n+q)log_2 ^2 n)$ 。

  1 //It is made by Awson on 2018.1.8
  2 #include <set>
  3 #include <map>
  4 #include <cmath>
  5 #include <ctime>
  6 #include <queue>
  7 #include <stack>
  8 #include <cstdio>
  9 #include <string>
 10 #include <vector>
 11 #include <cstdlib>
 12 #include <cstring>
 13 #include <iostream>
 14 #include <algorithm>
 15 #define LL long long
 16 #define RE register
 17 #define lowbit(x) ((x)&(-(x)))
 18 #define Max(a, b) ((a) > (b) ? (a) : (b))
 19 #define Min(a, b) ((a) < (b) ? (a) : (b))
 20 #define Swap(a, b) ((a) ^= (b), (b) ^= (a), (a) ^= (b))
 21 using namespace std;
 22 const int N = 150000;
 23 const int INF = ~0u>>1;
 24 void read(int &x) {
 25     char ch; bool flag = 0;
 26     for (ch = getchar(); !isdigit(ch) && ((flag |= (ch == '-')) || 1); ch = getchar());
 27     for (x = 0; isdigit(ch); x = (x<<1)+(x<<3)+ch-48, ch = getchar());
 28     x *= 1-2*flag;
 29 }
 30 
 31 int n, q, A, col[N+5], a, b, c;
 32 struct tt {int to, next, cost; }edge[(N<<1)+5];
 33 int path[N+5], top;
 34 struct arr {
 35     LL x, xlca; int id;
 36     arr() {}
 37     arr(LL _x, LL _xlca, int _id) {x = _x, xlca = _xlca, id = _id; }
 38     bool operator < (const arr &b) const {return id < b.id; }
 39 };
 40 vector<arr>d[N+5];
 41 int fa[N+5];
 42 LL ans;
 43 void add(int u, int v, int c) {
 44     edge[++top].to = v;
 45     edge[top].next = path[u];
 46     edge[top].cost = c;
 47     path[u] = top;
 48 }
 49 namespace LCA {
 50     int bin[25], lim, dep[N+5], fa[N+5][25];
 51     LL dis[N+5];
 52     void dfs(int o, int depth, int father, LL dist) {
 53     dis[o] = dist;
 54     fa[o][0] = father, dep[o] = depth; 
 55     for (int i = path[o]; i; i = edge[i].next)
 56         if (edge[i].to != father) dfs(edge[i].to, depth+1, o, dist+edge[i].cost);
 57     }
 58     int query(int x, int y) {
 59     if (dep[x] < dep[y]) Swap(x, y);
 60     for (int i = lim; i >= 0; i--) if (dep[fa[x][i]] >= dep[y]) x = fa[x][i];
 61     if (x == y) return x;
 62     for (int i = lim; i >= 0; i--) if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
 63     return fa[x][0];
 64     }
 65     LL dist(int x, int y) {return dis[x]+dis[y]-(dis[query(x, y)]<<1); }
 66     void main() {
 67     lim = log(n)/log(2), bin[0] = 1; for (int i = 1; i <= 20; i++) bin[i] = bin[i-1]<<1;
 68     dfs(1, 1, 0, 0);
 69     for (int t = 1; t <= lim; t++) for (int i = 1; i <= n; i++) fa[i][t] = fa[fa[i][t-1]][t-1];
 70     }
 71 }
 72 namespace Point_divide {
 73     int size[N+5], mx[N+5], vis[N+5], minsize, root;
 74     void get_size(int o, int fa) {
 75     size[o] = 1, mx[o] = 0;
 76     for (int i = path[o]; i; i = edge[i].next)
 77         if (edge[i].to != fa && !vis[edge[i].to]) {
 78         get_size(edge[i].to, o);
 79         size[o] += size[edge[i].to];
 80         if (size[edge[i].to] > mx[o]) mx[o] = size[edge[i].to];
 81         }
 82     }
 83     void get_root(int o, int pa, int fa) {
 84     mx[o] = Max(mx[o], size[pa]-size[o]);
 85     if (mx[o] < minsize) minsize = mx[o], root = o;
 86     for (int i = path[o]; i; i = edge[i].next)
 87         if (edge[i].to != fa && !vis[edge[i].to]) get_root(edge[i].to, pa, o);
 88     }
 89     void get_dist(int o, int rt, int pa, int fa, int dist) {
 90     d[rt].push_back(arr(dist, LCA::dist(pa, o), col[o]));
 91     for (int i = path[o]; i; i = edge[i].next)
 92         if (edge[i].to != fa && !vis[edge[i].to]) get_dist(edge[i].to, rt, pa, o, dist+edge[i].cost);
 93     }
 94     void solve(int o, int father) {
 95     minsize = INF; get_size(o, 0), get_root(o, o, 0);
 96     vis[root] = 1, fa[root] = father; d[root].push_back(arr(0, LCA::dist(root, father), col[root])); int rt = root;
 97     for (int i = path[root]; i; i = edge[i].next)
 98         if (!vis[edge[i].to]) get_dist(edge[i].to, root, father, root, edge[i].cost);
 99     d[root].push_back(arr(0, 0, -1));
100     sort(d[root].begin(), d[root].end()); int size = d[root].size();
101     for (int i = 1; i < size; i++) d[root][i].x += d[root][i-1].x, d[root][i].xlca += d[root][i-1].xlca;
102     for (int i = path[root]; i; i = edge[i].next)
103         if (!vis[edge[i].to]) solve(edge[i].to, rt);
104     }
105     void main() {solve(1, 0); }
106 }
107 int upperbound(int o, int key) {
108     int L = 0, R = d[o].size()-1, mid, ans = R;
109     while (L <= R) {
110     mid = (L+R)>>1;
111     if (d[o][mid].id <= key) L = mid+1, ans = mid;
112     else R = mid-1;
113     }
114     return ans;
115 }
116 int lowerbound(int o, int key) {
117     int L = 0, R = d[o].size()-1, mid, ans = 0;
118     while (L <= R) {
119     mid = (L+R)>>1;
120     if (d[o][mid].id <= key) L = mid+1, ans = mid;
121     else R = mid-1;
122     }
123     return ans;
124 }
125 void query(int o, int l, int r) {
126     for (int x = o, L, R; x; x = fa[x]) {
127     R = upperbound(x, r), L = lowerbound(x, l-1);
128     ans += (R-L)*LCA::dist(x, o);
129     if (fa[x]) ans -= d[x][R].xlca-d[x][L].xlca+(LL)(R-L)*LCA::dist(fa[x], o);
130     ans += d[x][R].x-d[x][L].x;
131     }
132 }
133 void work() {
134     read(n), read(q), read(A);
135     for (int i = 1; i <= n; i++) read(col[i]);
136     for (int i = 1; i < n; i++) read(a), read(b), read(c), add(a, b, c), add(b, a, c);
137     LCA::main(); Point_divide::main();
138     while (q--) {
139     read(c), read(a), read(b); a = (ans+a)%A, b = (ans+b)%A;
140     if (a > b) Swap(a, b); ans = 0;
141     query(c, a, b); printf("%lld
", ans);
142     }
143 }
144 int main() {
145     work();
146     return 0;
147 }
动态点分
原文地址:https://www.cnblogs.com/NaVi-Awson/p/8169022.html