【THUWC2020】某科学的动态仙人掌

考场上感觉是一道可做题,然后最后做法差不多,但是一直卡在 (dfn),没去搞 (bfn)

然后今天捡起这道题想了一下就想出来了。

因为没有交题链接,下面的做法不一定正确。(结论经过对拍,应该没问题)

这是一个俩 (log) 做法,不知道有没有更优的。


首先,这是一个区间点构成虚树的 (K) 连通块个数。其中 (K) 连通为当在原树上距离 (leq K) 就加一条无向边。

众所周知,虚树森林的连通块个数可以由 ( ext{点数} - ext{边数}) 得到。

所以可以得到一个 (trival)(O(n^2Q)) 的做法,即暴力连边,如果距离 (leq K) 就并查集连边。

但是显然过不了。


我们考虑缩小边的数量。具体的,如果能给每个点钦点一个必连的点(比如说贪心的在左边选一个最大的,在右边选一个最小的),然后做一个高维数点,那就优美了。

对于询问,给定点集,我们钦定点集中任意一个点 (u),对原树 (makeroot(u)),那么剩下点集中的点 (v),如果它最近的祖先 (a) 满足 (dis(v, a) leq K),那么连一条有向边 (a ightarrow v)

显然,对于这个点集, (u)(K) 连通块集合 (S) 满足对于所有点 (v in S)(u) 能到达 (v)

然而实际上 (v) 可以和任意一个满足距离限制条件的祖先 (a) 匹配,并不需要最近的。这样也是满足这个性质的。

那么,我们记一个 (Ans = r - l + 1) 作为初值,每次找集合 (S) 中的一个元素 (u) 作为根,并进行连边。记 (u) 能到达的除 (u) 外的点的集合为 (A),那么更新答案 (Ans' = Ans - |A|) ,更新集合 (S' = S ackslash (A cup {u}))

即每次更新完删除根的连通块。考虑这个过程,我们可以找到一个不变的关系:

如果每次都选 (bfs) 序最小的点更新,那么:

  1. 对于每次更新的连通块,我们连的边 ((u, v))(bfn_u < bfn_v)
  2. 对于根为 (u) 的一个连通块更新完后,接下来对于未被更新的 (v)(v)(u) 子树中,(bfn_v) 比这个连通块所有点的 (bfn) 都大。

这样,如果每个点只向 (bfn) 比它小的点连边,连通性不变。那么我们的答案就是 ( ext{点数} - ext{这张图的边数})

实际上,对于任意一个 (K),对距离 (leq K) 的点之间连边,得到的是一个弦图 (G)

一个经典的结论就是:对于一棵树,以及任意一个这样的 (K),用 (bfs) 得到的序列,就是 (G) 的完美消除序列。这也就印证了上面的那个性质。


现在问题转化为:

对于一个区间 ([l, r]),计算 (u) 的个数使得 (l leq u leq r) 且满足(存在一个不同于 (u)(l leq v leq r),使得 ((bfn_v < bfn_u)))。

显然,对于 (u) 只要选择 (bfn_v) 小于自己的 (v) 就行了。

显然是选编号差 (|u - v|) 最小的更好,可以更方便的统计。

所以现在就有了一个 (O(n^2 + nQ)) 的做法:

对每个点 (u),预处理 (v < u) 且满足 (dis(u, v) leq K, bfn_v < bfn_u) 的最大的 (v),记为 (L_u)。如果不存在记 (L_u = 0)

同理预处理 (v > u) 且满足 (dis(u, v) leq K, bfn_v < bfn_u) 的最小的 (v),记为 (R_u)。如果不存在记 (R_u = n + 1)

于是只需要求

[(r - l + 1) - sum_{l leq i leq r} left[(l leq L_i) lor (R_i leq r) ight] ]

显然 (lor) 是可以转变成 (1 - land),所以就要计算:

[sum_{l leq i leq r} left[(L_i < l) land (r < R_i) ight] ]

放到树上,也就是没有父亲的点个数。


我们先计算出 (L)(R)

不失一般性地考虑算 (L),对于 (u),我们需要计算满足:

  1. (dis(u, v) leq K)
  2. (bfn_v leq bfn_u)
  3. (v < u)

的最大的 (v)

首先,可以通过树分治或 dsu 满足条件 (1)

然后对于条件 (2)(3),可以使用树状数组套 (set),即支持前缀加元素,询问前缀的元素集合里元素的前驱。

因此可以轻松地做到 (O(nlog^3 n))

然而这太慢了。

使用树分治,考虑合并时,因为 (LCA) 固定,把 (dis) 拆成 (dep) 的限制。

使用双指针,把 (dep) 从小到大扫过去。此时要往集合动态插入元素。

维护一个序列数据结构,维护 (v = v') 时最小的满足条件的 (dfn),支持后缀元素取 (min),查询区间元素最小值,前缀单 (log) 二分。

因为前缀最小值是单调减的,所以二分正确性显然。当然这实际上就是一个单调栈。

这样就可以做到 (O(nlog^2 n)) 了。


计算出了 (L)(R),还有计算答案时。显然是一个三维数点。我们将询问拆成两部分。

那么要询问满足如下条件的 (v) 的个数:

  1. (v < A)
  2. (L_v < B)
  3. (R_v > C)

离线加树套树或使用分治解决。

复杂度 (O(n log^2 n))


综上,时间复杂度 (O(n log^2 n)),空间复杂度可以做到 (O(n))

#include <bits/stdc++.h>

const int MAXN = 300010;
int n, Q, K;
int head[MAXN], to[MAXN << 1], nxt[MAXN << 1], tot;
void addedge(int b, int e) {
	nxt[++tot] = head[b]; to[head[b] = tot] = e;
	nxt[++tot] = head[e]; to[head[e] = tot] = b;
}
int bfn[MAXN], dep[MAXN], buc[MAXN];
void dfs0(int u, int fa = 0) {
	++buc[dep[u]];
	for (int i = head[u]; i; i = nxt[i])
		if (to[i] != fa)
			dep[to[i]] = dep[u] + 1, dfs0(to[i], u);
}
int sz[MAXN], vis[MAXN], rt, rtv, all;
void getroot(int u, int fa = 0) {
	sz[u] = 1;
	int maxd = 0;
	for (int i = head[u]; i; i = nxt[i])
		if (!vis[to[i]] && to[i] != fa) {
			getroot(to[i], u);
			sz[u] += sz[to[i]];
			maxd = std::max(maxd, sz[to[i]]);
		}
	maxd = std::max(maxd, all - sz[u]);
	if (maxd < rtv) rtv = maxd, rt = u;
}
void _getroot(int u, int d) {
	rt = u, all = d, rtv = 0x3f3f3f3f, getroot(u);
}
typedef std::vector<int> vi;
typedef std::pair<int, int> pi;
typedef std::vector<pi> vpi;
vpi tree[MAXN];
struct cmp {
	bool operator () (int a, int b) {
		return tree[a].size() > tree[b].size();
	}
} ;
std::priority_queue<int, vi, cmp> q;
void calc(int u, vpi & tar, int dep = 1, int fa = 0) {
	tar.emplace_back(dep, u);
	for (int i = head[u]; i; i = nxt[i])
		if (to[i] != fa && !vis[to[i]])
			calc(to[i], tar, dep + 1, u);
}
int L[MAXN], R[MAXN];
const int INF = 0x3f3f3f3f;
int sgt[MAXN << 2];
void mdf(int tar, int v) {
	int u = 1, l = 1, r = n;
	while (l < r) {
		sgt[u] = std::min(sgt[u], v);
		int mid = l + r >> 1;
		if (tar <= mid) u = u << 1, r = mid;
		else u = u << 1 | 1, l = mid + 1;
	}
	sgt[u] = std::min(sgt[u], v);
}
void clr(int tar) {
	int u = 1, l = 1, r = n;
	while (l < r) {
		sgt[u] = INF;
		int mid = l + r >> 1;
		if (tar <= mid) u = u << 1, r = mid;
		else u = u << 1 | 1, l = mid + 1;
	}
	sgt[u] = INF;
}
int qryl(int R, int v, int u = 1, int l = 1, int r = n) {
	if (sgt[u] > v) return 0;
	if (r <= R) {
		while (l < r) {
			int mid = l + r >> 1;
			if (sgt[u << 1 | 1] <= v) u = u << 1 | 1, l = mid + 1;
			else u = u << 1, r = mid;
		}
		return l;
	}
	int mid = l + r >> 1;
	if (R > mid) if (int t = qryl(R, v, u << 1 | 1, mid + 1, r)) return t;
	return qryl(R, v, u << 1, l, mid);
}
int qryr(int L, int v, int u = 1, int l = 1, int r = n) {
	if (sgt[u] > v) return 0;
	if (L <= l) {
		while (l < r) {
			int mid = l + r >> 1;
			if (sgt[u << 1] <= v) u = u << 1, r = mid;
			else u = u << 1 | 1, l = mid + 1;
		}
		return l;
	}
	int mid = l + r >> 1;
	if (L <= mid) if (int t = qryr(L, v, u << 1, l, mid)) return t;
	return qryr(L, v, u << 1 | 1, mid + 1, r);
}
void solve(int u) {
	vis[u] = true; int bak;
	tree[bak = 1].emplace_back(0, u);
	for (int i = head[u]; i; i = nxt[i]) if (!vis[to[i]])
		calc(to[i], tree[++bak]);
	for (int i = 1; i <= bak; ++i) q.push(i);
	auto make = [] (vpi & x, vpi & y) {
		int cur = 0, SZ = x.size();
		std::reverse(y.begin(), y.end());
		for (auto t : y) {
			while (cur < SZ && x[cur].first + t.first <= K) {
				int u = x[cur].second;
				mdf(u, bfn[u]);
				++cur;
			}
			int u = t.second, atl = qryl(u, bfn[u]), atr = qryr(u, bfn[u]);
			if (atl < u) L[u] = std::max(L[u], atl);
			if (atr > u) R[u] = std::min(R[u], atr);
		}
		for (int i = 0; i < cur; ++i) clr(x[i].second);
		std::reverse(y.begin(), y.end());
	};
	while (q.size() > 1) {
		int x = q.top(); q.pop();
		int y = q.top(); q.pop();
		std::sort(tree[x].begin(), tree[x].end());
		std::sort(tree[y].begin(), tree[y].end());
		make(tree[x], tree[y]); make(tree[y], tree[x]);
		if (tree[x].size() < tree[y].size()) std::swap(x, y);
		for (auto t : tree[y]) tree[x].push_back(t);
		tree[y].clear();
		q.push(x);
	}
	while (!q.empty()) q.pop();
	for (int i = 1; i <= bak; ++i) tree[i].clear();
	for (int i = head[u]; i; i = nxt[i])
		if (!vis[to[i]]) {
			getroot(to[i]);
			_getroot(to[i], sz[to[i]]);
			solve(rt);
		}
}
inline int absx(int x) { return x < 0 ? -x : x ; }
namespace divide {
	struct qry {
		int typ, a, b, c;
	} qs[MAXN * 5];
	int bak, ansl[MAXN * 2], tree[MAXN];
	void solve(qry * l, qry * r) {
		if (l + 1 >= r) return ;
		qry * mid = l + (r - l >> 1);
		solve(l, mid); solve(mid, r);
		qry * lhs = l, * rhs = mid;
		while ((lhs < mid) || (rhs < r)) {
			if (rhs == r || (lhs < mid && lhs -> b < rhs -> b)) {
				if (lhs -> typ == 0) {
					for (int x = lhs -> c; x; x &= x - 1) ++tree[x];
				}
				++lhs;
			} else {
				if (rhs -> typ != 0) {
					int at = rhs -> typ;
					int coef = absx(at) / at, res = 0;
					at *= coef;
					for (int x = rhs -> c + 1; x <= n + 1; x += x & -x) res += tree[x];
					ansl[at] += coef * res;
				}
				++rhs;
			}
		}
		for (qry * it = l; it != mid; ++it) if (it -> typ == 0)
			for (int x = it -> c; x; x &= x - 1) --tree[x];
		std::sort(l, r, [] (qry a, qry b) {
			return a.b == b.b ? absx(a.typ) < absx(b.typ) : a.b < b.b;
		});
	}
	void solve() {
		std::sort(qs + 1, qs + 1 + bak, [] (qry a, qry b) {
			return a.a == b.a ? absx(a.typ) < absx(b.typ) : a.a < b.a;
		});
		solve(qs + 1, qs + 1 + bak);
	}
}
int main() {
	std::ios_base::sync_with_stdio(false), std::cin.tie(0);
	memset(sgt, 0x3f, sizeof sgt);
	std::cin >> n >> Q >> K;
	for (int i = 1; i <= n; ++i) R[i] = n + 1;
	for (int i = 2, t; i <= n; ++i)
		std::cin >> t, addedge(i, t);
	dep[1] = 1; dfs0(1);
	for (int i = 1; i <= n; ++i) buc[i] += buc[i - 1];
	for (int i = n; i; --i) bfn[i] = buc[dep[i]]--;
	_getroot(1, n); solve(rt);
	for (int i = 1; i <= n; ++i)
		divide::qs[++divide::bak] = (divide::qry) {0, i, L[i], R[i]};
	for (int i = 1, l, r; i <= Q; ++i) {
		std::cin >> l >> r;
		divide::qs[++divide::bak] = (divide::qry) {i, r, l, r};
		divide::qs[++divide::bak] = (divide::qry) {-i, l - 1, l, r};
	}
	divide::solve();
	for (int i = 1; i <= Q; ++i)
		std::cout << divide::ansl[i] << '
';
	return 0;
}
原文地址:https://www.cnblogs.com/daklqw/p/12124560.html