一种关于子树询问的有趣做法

jys posted @ Mar 19, 2014 08:42:52 PM in 未分类 , 722 阅读

给一棵树,每个节点有权值,有若干询问,每次询问某个节点的子树中出现次数大于y的权值x的个数

dfs序上裸莫队可以O(nsqrtn),平衡树启发式合并O(nlog2n),线段树合并O(nlogn),这种类似启发式合并的做法也是O(nlogn)的

sy2006的代码:

#include <cstdio>
#include <algorithm>
#define NE(x, y) ++ne, e[ne]=y, h[ne]=s[x], s[x]=ne
using namespace std;

inline void R(int &x) {
	char ch = getchar(); x = 0;
	for (; ch<'0'; ch = getchar());
	for (; ch>='0'; ch=getchar()) x = x*10 + ch-'0';
}
const int N = 100005;
struct query {
	int k, ans;
} q[N];
int n, m, ne = 0, vt = 0,
	col[N], l[N], st[N], ed[N], sz[N], cnt[N], sum[N],
	s[N], e[N*2], h[N*2], sq[N], hq[N];
void size(int x, int f) {
	sz[x] = 1;
	for (int w=s[x]; w; w=h[w]) if (e[w]!=f)
		size(e[w], x), sz[x] += sz[e[w]];
}
inline void ins(int x) {
	for (int i=st[x]; i<=ed[x]; ++i)
		++sum[++cnt[l[i]]];
}
inline void del(int x) {
	for (int i=st[x]; i<=ed[x]; ++i)
		--sum[cnt[l[i]]--];
}
void solve(int x, int f) {
	st[x] = ++vt; l[vt] = col[x];
	int mx = 0, mf = 0;
	for (int w=s[x]; w; w=h[w]) if (e[w]!=f)
		if (sz[e[w]] > mx)
			mx = sz[e[w]], mf = e[w];
	for (int w=s[x]; w; w=h[w]) if (e[w]!=f && e[w]!=mf)
		solve(e[w], x), del(e[w]);
	if (mx) solve(mf, x);
	for (int w=s[x]; w; w=h[w]) if (e[w]!=f && e[w]!=mf)
		ins(e[w]);
	++sum[++cnt[col[x]]];
	for (int w=sq[x]; w; w=hq[w])
		q[w].ans = sum[q[w].k];
	ed[x] = vt;
}
int main() {
	R(n); R(m);
	for (int i=1; i<=n; ++i) R(col[i]);
	int x, y;
	for (int i=1; i<n; ++i) {
		R(x); R(y);
		NE(x, y); NE(y, x);
	}
	for (int i=1; i<=m; ++i) {
		R(x); R(q[i].k);
		hq[i] = sq[x], sq[x] = i;
	}
	size(1, 0);
	solve(1, 0);
	for (int i=1; i<=m; ++i)
		printf("%d\n", q[i].ans);
	return 0;
}

基本框架十分清晰

考虑每个点被删除的次数,某个点被删一次一定是作为一个轻儿子被删,在与重儿子合并之后所在联通块大小至少*2,故每个点至多被删logn次,所以复杂度保证在O(nlogn)

可以用神奇的复杂度做一些很暴力的事情

感觉跟线段树的合并差不多,不过这个的空间复杂度小多了

这种写法要离线,如果用可持续化线段树维护那个全局数组的话就可以搞成在线。。不过这样跟线段树合并相比并没有什么优势


登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter