A Simple Problem(路径)

题面

有一棵 \(n\) 个点的树,每个点有一个颜色,编号为 \(i\) 的点的颜色为 \(col[i]\)

给定 \(m\) 组询问,每次询问给定 \(u,v,a,b\),表示询问在 \(u\)\(v\) 的简单路径上,出现次数 \(\in[a,b]\) 的颜色有多少种。

限制

测试点编号 特殊限制 分值
\(1\sim2\) \(n,m\le 1000\) \(10\)
\(3\sim8\) \(n,m\le 50000\) \(30\)
\(9\sim12\) \(a=b\) \(20\)
\(13\sim16\) \(b=n\) \(20\)
\(16\sim20\) \(20\)

对于 \(100\%\) 的数据,满足 \(n,m\le 2\times 10^5\)\(col[i]\le 10^9\)\(0\le a\le b\le n\)

时间限制为 \(2s\)

题解

10pts

每次询问让 \(u,v\) 暴力向上跳到 \(LCA\),在这个过程中统计颜色的出现次数。然后遍历所有颜色,统计答案即可。

离散化后颜色的值域是 \(O(n)\) 的,时间复杂度 \(O(nm)\)

40pts

树上莫队:求出树的欧拉序(进出一个点时都要加入这个点的标号),记录进出时的时间戳分别为 \(L[x],R[x]\)。然后对于一条路径 \(u-LCA-v\),默认 \(L[u]\le L[v]\),我们进行分类讨论

  1. \(LCA=u\),这时路径上的点集为:在欧拉序下标为 \([L[u],L[v]]\) 之间,且只出现 \(1\) 次的点。
  2. \(LCA\not=u\),这时路径上的点集为:在欧拉序下标为 \([R[u],L[v]]\) 之间,且只出现 \(1\) 次的点。这样统计不到 \(LCA\),需要特判一个点。

然后我们就可以用莫队来维护路径信息。回到这题上,考虑维护出现次数,每次莫队区间增加/删除一个数,需要单点加/减,询问相当于一个区间和。用树状数组维护即可。

时间复杂度 \(O(n\sqrt{m}\log{n})\), 可以通过 \(n,m\le 50000\) 的测试点。不出意外的话,这个算法是卡不过去的

60pts

对于 \(a=b\) 的特殊性质,相当于询问出现次数为 \(a\) 的颜色数,直接在莫队转移的时候 \(O(1)\) 统计即可。时间复杂度 \(O(n\sqrt{m})\)

结合前面的算法可以获得 \(\texttt{60pts}\)

80pts

对于 \(b=n\) 的特殊性质,相当于询问出现次数 \(\ge a\) 的颜色数,这是对正解的一个提示

\(ge[i]\) 表示出现次数 \(\ge i\) 的颜色数量,考虑到莫队算法每次只会有一种颜色的出现次数 \(\pm 1\) 的变化,我们可以直接 \(O(1)\) 维护这个信息:设 \(cnt(c)\) 表示当前颜色 \(c\) 的出现次数,则

  • 颜色 \(c\) 出现次数增加 \(1\),则 \(ge[]\) 唯一的变化是 \(\texttt{ge[cnt(c)+1]++}\)
  • 颜色 \(c\) 出现次数减少 \(1\),则 \(ge[]\) 唯一的变化是 \(\texttt{ge[cnt(c)]--}\)

细节:注意在莫队之前初始化 \(ge[0]\)

这样的时间复杂度是 \(O(n\sqrt{m})\)

100pts(算法一)

\(\texttt{80pts}\) 的解法中,我们已经可以 \(O(n\sqrt{m})\) 维护出现次数 \(\ge k\) 的信息,于是我们将每个询问 \([a, b]\) 看作出现 \(\ge a\) 减去出现 \(\ge b+1\) 即可,时间复杂度仍然是 \(O(n\sqrt{m})\)

  • 回想树状数组的那个解法,可以发现树状数组维护了前缀,而这个做法维护的是后缀。

100pts(算法二)

莫队的指针移动次数为 \(O(n\sqrt{m})\),而询问是 \(O(m)\) 的。所以我们可以考虑根号平衡:设计一个 \(O(1)\) 更新颜色出现次数,\(O(\sqrt{n})\) 查询出现次数 \(\ge k\) 的颜色数量的数据结构。(当然也可以查询 \(\le k\)

对出现次数分块,每次修改相当于一个单点 \(-1\) 和一个单点 \(+1\),同时维护块内和(代表出现次数在这个块中的颜色数),这些是可以 \(O(1)\) 做到的。

对于询问,我们 \(O(\sqrt{n})\) 跳到 \(k\) 对应块,然后暴力统计块内零散点,再暴力统计后面的整块即可,复杂度是 \(O(\sqrt{n})\) 的。

总时间复杂度 \(O(n\sqrt{m}+m\sqrt{n})\),期望得分 \(100\)。数据给的 \(n,m\) 同级,所以并没有卡这个算法的意思。

代码

std,$O(n\sqrt{m})$
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 5, M = 5e5 + 5;
struct Query {
	int id, l, r, a, b, lca;
} q[M];
int n, m, col[N], ans[M];
int num[N], numtot;
vector<int> adj[N];
int fa[N], son[N], siz[N], dep[N], top[N], L[N], R[N], id[N * 2], dfntot;
int len, bel[N * 2], cnt[N], ge[N * 2], tim[N];
void dfs1(int x, int f) {
	fa[x] = f;
	dep[x] = dep[f] + 1;
	siz[x] = 1;
	for(auto y : adj[x]) {
		if(y != f) {
			dfs1(y, x);
			siz[x] += siz[y];
			if(siz[y] > siz[son[x]]) {
				son[x] = y;
			}
		}
	}
}
void dfs2(int x, int t) {
	top[x] = t;
	L[x] = ++dfntot;
	id[dfntot] = x;
	if(son[x]) {
		dfs2(son[x], t);
	}
	for(int y : adj[x]) {
		if(y == fa[x] || y == son[x]) {
			continue;
		}
		dfs2(y, y);
	}
	R[x] = ++dfntot;
	id[dfntot] = x;
}
int LCA(int x, int y) { // 求LCA
	while(top[x] != top[y]) {
		if(dep[fa[top[x]]] < dep[fa[top[y]]]) {
			swap(x, y);
		}
		x = fa[top[x]];
	}
	return dep[x] < dep[y] ? x : y;
}
void update(int x) {
	// tim[x]表示点x当前出现1次还是2次
	if(tim[x]) {
		--ge[cnt[col[x]]]; // ge[i]表示出现次数 >= i 的颜色数量
		--cnt[col[x]]; // cnt[i]表示颜色i的出现次数
	} else {
		++cnt[col[x]];
		++ge[cnt[col[x]]];
	}
	tim[x] ^= 1;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) {
		cin >> col[i];
		num[++numtot] = col[i];
	}
	sort(num + 1, num + numtot + 1);
	numtot = unique(num + 1, num + numtot + 1) - num - 1;
	for(int i = 1; i <= n; ++i) {
		col[i] = lower_bound(num + 1, num + numtot + 1, col[i]) - num; // 离散化
	}
	for(int i = 1; i < n; ++i) {
		int u, v;
		cin >> u >> v;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}
	dfs1(1, 0);
	dfs2(1, 1); 
	for(int i = 1; i <= m; ++i) {
		int u, v, a, b;
		cin >> u >> v >> a >> b;
		if(L[u] > L[v]) {
			swap(u, v);
		}
		int lca = LCA(u, v);
		if(lca == u) {
			q[i] = {i, L[u], L[v], a, b, 0};
		} else {
			q[i] = {i, R[u], L[v], a, b, lca};
		}
	}
	// 莫队
	len = max(1, int(2 * n / sqrt(m)));
	for(int i = 1; i <= 2 * n; ++i) {
		bel[i] = (i - 1) / len + 1;
	}
	sort(q + 1, q + m + 1, [&](auto lhs, auto rhs) {
		return (bel[lhs.l] ^ bel[rhs.l]) ? bel[lhs.l] < bel[rhs.l] : ((bel[lhs.l] & 1) ? lhs.r < rhs.r : lhs.r > rhs.r);
	});
	int l = 1, r = 0;
	ge[0] = numtot; // 出现次数 >= 0 的颜色有numtot种
	for(int i = 1; i <= m; ++i) {
		while(q[i].l < l) {
			update(id[--l]);
		}
		while(r < q[i].r) {
			update(id[++r]);
		}
		while(l < q[i].l) {
			update(id[l++]);
		}
		while(q[i].r < r) {
			update(id[r--]);
		}
		if(q[i].lca) {
			update(q[i].lca);
		}
		ans[q[i].id] = ge[q[i].a] - ge[q[i].b + 1];
		if(q[i].lca) {
			update(q[i].lca);
		}
	}
	for(int i = 1; i <= m; ++i) {
		cout << ans[i] << "\n";
	}
	return 0;
} 

40pts,$O(n\sqrt{m}\log{n})$
#include<bits/stdc++.h>
using namespace std;
using i64 = long long;
const int N = 5e5 + 5, M = 5e5 + 5;
struct Query {
	int id, l, r, a, b, lca;
} q[M];
struct Fenwick {
	int n, sum[N];
	void add(int x, int v) {
		++x;
		while(x <= n) {
			sum[x] += v;
			x += x & -x;
		}
		sum[x] += v;
	}
	int ask(int x) {
		++x;
		int res = 0;
		while(x) {
			res += sum[x];
			x -= x & -x;
		}
		return res;
	}
	int ask(int l, int r) {
		return ask(r) - ask(l - 1);
	}
} fen;
int n, m, col[N], ans[M];
int num[N], numtot;
vector<int> adj[N];
int fa[N], son[N], siz[N], dep[N], top[N], L[N], R[N], id[N * 2], dfntot;
int len, bel[N * 2], cnt[N], tim[N];
void dfs1(int x, int f) {
	fa[x] = f;
	dep[x] = dep[f] + 1;
	siz[x] = 1;
	for(auto y : adj[x]) {
		if(y != f) {
			dfs1(y, x);
			siz[x] += siz[y];
			if(siz[y] > siz[son[x]]) {
				son[x] = y;
			}
		}
	}
}
void dfs2(int x, int t) {
	top[x] = t;
	L[x] = ++dfntot;
	id[dfntot] = x;
	if(son[x]) {
		dfs2(son[x], t);
	}
	for(int y : adj[x]) {
		if(y == fa[x] || y == son[x]) {
			continue;
		}
		dfs2(y, y);
	}
	R[x] = ++dfntot;
	id[dfntot] = x;
}
int LCA(int x, int y) {
	while(top[x] != top[y]) {
		if(dep[fa[top[x]]] < dep[fa[top[y]]]) {
			swap(x, y);
		}
		x = fa[top[x]];
	}
	return dep[x] < dep[y] ? x : y;
}
void update(int x) {
	fen.add(cnt[col[x]], -1);
	if(tim[x]) {
		--cnt[col[x]];
	} else {
		++cnt[col[x]];
	}
	fen.add(cnt[col[x]], 1);
	tim[x] ^= 1;
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= n; ++i) {
		cin >> col[i];
		num[++numtot] = col[i];
	}
	sort(num + 1, num + numtot + 1);
	numtot = unique(num + 1, num + numtot + 1) - num - 1;
	for(int i = 1; i <= n; ++i) {
		col[i] = lower_bound(num + 1, num + numtot + 1, col[i]) - num;
	}
	for(int i = 1; i < n; ++i) {
		int u, v;
		cin >> u >> v;
		adj[u].push_back(v);
		adj[v].push_back(u);
	}
	dfs1(1, 0);
	dfs2(1, 1);
	for(int i = 1; i <= m; ++i) {
		int u, v, a, b;
		cin >> u >> v >> a >> b;
		assert(0 <= a && a <= b && b <= n);
		if(L[u] > L[v]) {
			swap(u, v);
		}
		int lca = LCA(u, v);
		if(lca == u) {
			q[i] = {i, L[u], L[v], a, b, 0};
		} else {
			q[i] = {i, R[u], L[v], a, b, lca};
		}
	}
	len = max(1, int(2 * n / sqrt(m)));
	for(int i = 1; i <= 2 * n; ++i) {
		bel[i] = (i - 1) / len + 1;
	}
	sort(q + 1, q + m + 1, [&](auto lhs, auto rhs) {
		return (bel[lhs.l] ^ bel[rhs.l]) ? bel[lhs.l] < bel[rhs.l] : ((bel[lhs.l] & 1) ? lhs.r < rhs.r : lhs.r > rhs.r);
	});
	fen.n = n + 1;
	fen.add(0, numtot);
	int l = 1, r = 0;
	for(int i = 1; i <= m; ++i) {
		while(q[i].l < l) {
			update(id[--l]);
		}
		while(r < q[i].r) {
			update(id[++r]);
		}
		while(l < q[i].l) {
			update(id[l++]);
		}
		while(q[i].r < r) {
			update(id[r--]);
		}
		if(q[i].lca) {
			update(q[i].lca);
		}
		ans[q[i].id] = fen.ask(q[i].a, q[i].b);
		if(q[i].lca) {
			update(q[i].lca);
		}
	}
	for(int i = 1; i <= m; ++i) {
		cout << ans[i] << "\n";
	}
	return 0;
}
10pts,$O(nm)$
自己动手,丰衣足食。
posted @ 2022-07-16 17:52  hzy1  阅读(14)  评论(0编辑  收藏  举报