Description
一个树,边都是单位长度。
你有 Q Q 个询问,每个询问形如:
- 假如在树上放个警卫,第
i
i
个警卫放在点,它可以看守到距离
xi x i 点距离不超过 ri r i 的所有点。 - 问这些警卫一起可以看到多少个点。
每个询问都是独立的。
- n⩽50000 n ⩽ 50000 ,询问的警卫总数不超过 500000 500000 。
Solution
考虑m=1的情况
如果 m=1 m = 1 ,则意味着只有一名警卫。那么询问就等价于查询距离一个节点小于 range r a n g e 的节点数。
这就成为了一道点分树的板子题,在点分树上的每个节点维护 A,B A , B 两个 vector v e c t o r , 分别表示节点 u u 在点分树上的子树信息,与在原树上的子树信息。:在点分树中,以u为根的点分树子树距离点分树根节点( u u )不超过的节点数。
Bu,i B u , i :在原树中,以u为根的点分树子树距离此子树在原树中的根( fau f a u 的对应儿子)节点( u u )不超过的节点数。
每次计算距离节点 u u 不超过的节点数时的时候就暴力跳父节点,加上 A[r−d] A [ r − d ] 减去对应子节点的 B[r−d−1] B [ r − d − 1 ] 。
考虑一般的询问
∑m⩽500000 ∑ m ⩽ 500000 ,很容易想到建虚树。
现在考虑一般的询问如何计算,首先更新每个在虚树上的节点能警卫到的范围(虚树中添加的lca节点以及在某些情况下, rb<ra−dist(a,b) r b < r a − d i s t ( a , b ) ).
更新后直接按前文所述累加每个点能警戒到的范围。但是这样有的节点被重复统计,考虑一条边 (u,v) ( u , v ) , z z 是这条变的中点(即)。
而 z z 节点有一个性质,跨过后用 u u 比优,否则用 z z 比用优。
所以这条边重复覆盖的部分就是 u u 跨过警戒的范围, v v 跨过警戒的范围。即 z z 能警戒的范围。所以减去能警戒的范围即可。
#include <bits/stdc++.h> using namespace std; const int maxn = 100005; struct edge { int to, next; }e[maxn * 2]; int h[maxn], tot, n, m, q, p[maxn], r[maxn], ans; inline void add(int u, int v) { e[++tot] = (edge) {v, h[u]}; h[u] = tot; e[++tot] = (edge) {u, h[v]}; h[v] = tot; } inline int gi() { char c = getchar(); while(c < '0' || c > '9') c = getchar(); int sum = 0; while('0' <= c && c <= '9') sum = sum * 10 + c - 48, c = getchar(); return sum; } //树链剖分求lca int siz[maxn], dep[maxn], son[maxn], dfn[maxn], Time, fa[maxn], g[maxn], order[maxn]; inline void dfs1(int u) //链剖dfs1 { dep[u] = dep[fa[u]] + 1; siz[u] = 1; for(int i = h[u], v; i; i = e[i].next) if((v = e[i].to) != fa[u]) { fa[v] = u; dfs1(v); siz[u] += siz[v]; if(siz[v] > siz[son[u]]) son[u] = v; } } inline void dfs2(int u) //链剖dfs2 { order[dfn[u] = ++Time] = u; if(son[u]) { g[son[u]] = g[u]; dfs2(son[u]); for(int i = h[u], v; i; i = e[i].next) if((v = e[i].to) != fa[u] && v != son[u]) { g[v] = v; dfs2(v); } } } inline int lca(int u, int v) { while(g[u] != g[v]) { if(dep[g[u]] > dep[g[v]]) u = fa[g[u]]; else v = fa[g[v]]; } return dep[u] < dep[v] ? u : v; } //构建点分树 vector<int> A[maxn], B[maxn]; //A:点分树子树信息,B:原树子树信息 int rt[maxn][20], d[maxn][20], len[maxn]; int cent, Min, sum; bool vis[maxn]; void getroot(int u, int fa) //找重心 { int Mxsz = 0; siz[u] = 1; for(int i = h[u], v; i; i = e[i].next) if((v = e[i].to) != fa && !vis[v]) { getroot(v, u); siz[u] += siz[v]; Mxsz = max(Mxsz, siz[v]); } Mxsz = max(Mxsz, sum - siz[u]); if(Mxsz < Min) Min = Mxsz, cent = u; } void dfs3(int u,int fa, int dis, int root, vector<int> &v) //统计子树信息 { rt[u][len[u]] = root; d[u][len[u]++] = dis; if(dis == (int)v.size()) v.push_back(u <= n); else v[dis] += u <= n; for(int i = h[u]; i; i = e[i].next) if(e[i].to != fa && !vis[e[i].to]) dfs3(e[i].to, u, dis + 1, root, v); } void dfs4(int u, int fa, int dis, vector<int> &v) { if(dis == (int)v.size()) v.push_back(u <= n); else v[dis] += u <= n; for(int i = h[u]; i; i = e[i].next) if(e[i].to != fa && !vis[e[i].to]) dfs4(e[i].to, u, dis + 1, v); } void solve(int u, vector<int> &v) //构建点分树主过程 { Min = n << 1; getroot(u, 0); vis[u = cent] = true; B[u] = v; dfs3(u, 0, 0, u, A[u]); for(int len = A[u].size(), i = 1; i < len; ++i) A[u][i] += A[u][i - 1]; for(int i = h[u], v; i; i = e[i].next) if(!vis[v = e[i].to]) { vector<int> t(1, 0); dfs4(v, 0, 1, t); for(int len = t.size(), i = 1; i < len; ++i) t[i] += t[i - 1]; sum = siz[v]; solve(v, t); } } //建虚树 int stk[maxn], top, pre[maxn]; inline bool cmp1(const int &a, const int &b) {return dfn[a] > dfn[b];} inline bool cmp2(const int &a, const int &b) {return dfn[a] < dfn[b];} void build_tree() { sort(p + 1, p + m + 1, cmp1); stk[top = 1] = p[m]; for(int i = m - 1, x; i >= 1; --i) { int u = lca(p[i], stk[top]); for(x = 0; dfn[stk[top]] > dfn[u]; x = stk[top--]) if(x) pre[x] = stk[top]; if(stk[top] != u) stk[++top] = p[++m] = u, r[u] = -1; if(x) pre[x] = stk[top]; stk[++top] = p[i]; } --top; while(top) pre[stk[top + 1]] = stk[top], --top; sort(p + 1, p + m + 1, cmp2); } inline int calc(int u, int dis) //计算距离u不超过dis的节点数 { int s = 0; for(int i = 0; i < len[u]; ++i) { if(i && dis >= d[u][i - 1]) s -= B[rt[u][i]][min(dis - d[u][i - 1], (int)B[rt[u][i]].size() - 1)]; if(dis >= d[u][i]) s += A[rt[u][i]][min(dis - d[u][i], (int)A[rt[u][i]].size() - 1)]; } return s; } inline int jump(int u, int d) { while(dep[g[u]] > d) u = fa[g[u]]; return order[dfn[u] - (dep[u] - d)]; } int main() { freopen("tree.in", "r", stdin); freopen("tree.out", "w", stdout); n = gi(); for(int i = 1; i < n; ++i) { add(gi(), n + i); add(gi(), n + i); } sum = 2 * n - 1; dfs1(1); Time = 0; g[1] = 1; dfs2(1); vector<int> v; solve(1, v); q = gi(); for(int i = 1; i <= q; ++i) { m = gi(); for(int i = 1; i <= m; ++i) p[i] = gi(), r[p[i]] = gi() << 1; build_tree(); for(int i = m; i > 1; --i) r[pre[p[i]]] = max(r[pre[p[i]]], r[p[i]] - (dep[p[i]] - dep[pre[p[i]]])); for(int i = 2; i <= m; ++i) r[p[i]] = max(r[p[i]], r[pre[p[i]]] - (dep[p[i]] - dep[pre[p[i]]])); ans = 0; for(int i = 1; i <= m; ++i) ans += calc(p[i], r[p[i]]); for(int i = 2, k; i <= m; ++i) { k = jump(p[i], (r[pre[p[i]]] - r[p[i]] + dep[p[i]] + dep[pre[p[i]]]) >> 1); List(k, r[p[i]] - (dep[p[i]] - dep[k])); ans -= calc(k, r[p[i]] - (dep[p[i]] - dep[k])); } printf("%d\n", ans); } return 0; }
- 假如在树上放个警卫,第
i
i
个警卫放在点,它可以看守到距离