Description
一个树,边都是单位长度。
你有QQ个询问,每个询问形如:
- 假如在树上放个警卫,第ii个警卫放在点,它可以看守到距离
xixi点距离不超过riri的所有点。 - 问这些警卫一起可以看到多少个点。
每个询问都是独立的。
- n⩽50000n⩽50000,询问的警卫总数不超过 500000500000 。
Solution
考虑m=1的情况
如果m=1m=1,则意味着只有一名警卫。那么询问就等价于查询距离一个节点小于rangerange的节点数。
这就成为了一道点分树的板子题,在点分树上的每个节点维护A,BA,B两个vectorvector, 分别表示节点uu在点分树上的子树信息,与在原树上的子树信息。:在点分树中,以u为根的点分树子树距离点分树根节点(uu)不超过的节点数。
Bu,iBu,i:在原树中,以u为根的点分树子树距离此子树在原树中的根(faufau的对应儿子)节点(uu)不超过的节点数。
每次计算距离节点uu不超过的节点数时的时候就暴力跳父节点,加上A[r−d]A[r−d]减去对应子节点的B[r−d−1]B[r−d−1]。
考虑一般的询问
∑m⩽500000∑m⩽500000,很容易想到建虚树。
现在考虑一般的询问如何计算,首先更新每个在虚树上的节点能警卫到的范围(虚树中添加的lca节点以及在某些情况下,rb<ra−dist(a,b)rb<ra−dist(a,b)).
更新后直接按前文所述累加每个点能警戒到的范围。但是这样有的节点被重复统计,考虑一条边(u,v)(u,v), zz是这条变的中点(即)。
而zz节点有一个性质,跨过后用uu比优,否则用zz比用优。
所以这条边重复覆盖的部分就是uu跨过警戒的范围,vv跨过警戒的范围。即zz能警戒的范围。所以减去能警戒的范围即可。
#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; }
- 假如在树上放个警卫,第ii个警卫放在点,它可以看守到距离