zoukankan      html  css  js  c++  java
  • 【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;
    }
    
  • 相关阅读:
    2020前端学习路线 之完结篇
    axios 请求超时,设置重新请求的完美解决方法
    如何终止前端发起的请求?
    轮询与长轮询
    最全React技术栈技术资料汇总(收藏)
    React 服务端渲染完美的解决方案
    将数组格式的字符串转换成数组
    Cannot read property 'map' of undefined
    计算机编码方式简介
    python01之文件处理
  • 原文地址:https://www.cnblogs.com/daklqw/p/12124560.html
Copyright © 2011-2022 走看看