CSU 1811 Tree Intersection

Description

Bobo has a tree with n vertices numbered by 1,2,…,n and (n-1) edges. The i-th vertex has color c i, and the i-th edge connects vertices a i and b i.
Let C(x,y) denotes the set of colors in subtree rooted at vertex x deleting edge (x,y).
Bobo would like to know R_i which is the size of intersection of C(a i,b i) and C(b i,a i) for all 1≤i≤(n-1). (i.e. |C(a i,b i)∩C(b i,a i)|)

Input

The input contains at most 15 sets. For each set:
The first line contains an integer n (2≤n≤10 5).
The second line contains n integers c 1,c 2,…,c n (1≤c_i≤n).
The i-th of the last (n-1) lines contains 2 integers a i,b i (1≤a i,b i≤n).

Output

For each set, (n-1) integers R 1,R 2,…,R n-1.

Sample Input

4
1 2 2 1
1 2
2 3
3 4
5
1 1 2 1 2
1 3
2 3
3 5
4 5

Sample Output

1
2
1
1
1
2

1

求以每条边分开的两部分子树中共有的颜色的种数。

随便找个节点为根,问题可以转化成求某个子树的颜色种数减去只有该子树的拥有的颜色种数。

把树的dfs序弄出来,子树问题转换成区间问题,统计区间内的不同数字个数,以及只有该区间内有的数字的个数。

用树状数组统计即可,将询问按右端点排序。

从左向右扫描,对于第一个问题,记录下每个数字的之前出现的位置pre,

对于右端点在i的询问,数字的d[i]对于第一个问题的贡献是左端点在[pre+1,i]这段区间的值会加1。

对于第二个问题,统计下每个数字出现位置的最左端和最右端(L,R),

只有当扫到最右端R时,会让左端点在[1,L]的询问+1

上述两个问题相互之间没有干扰,可以直接在同一个树状数组的中解决。

当然,这是个无修改的区间问题,用莫队算法的更好想一些,只不过效率稍低。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define rep(i,j,k) for (int i = j; i <= k; i++)
#define per(i,j,k) for (int i = j; i >= k; i--)
#define loop(i,j,k) for (int i = j;i != -1; i = k[i])
#define lson x << 1, l, mid
#define rson x << 1 | 1, mid + 1, r
#define ff first
#define ss second
#define mp(i,j) make_pair(i,j)
#define pb push_back
#define pii pair
#define in(x) scanf("%d", &x);
using namespace std;
typedef long long LL;
const int low(int x) { return x&-x; }
const double eps = 1e-9;
const int INF = 0x7FFFFFFF;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
int n, col[N], x, y;
int ft[N], nt[N], u[N], v[N], sz;
int Ft[N], Nt[N], U[N], V[N], Sz;
int d[N], t, pre[N], L[N], R[N];
int f[N], ans[N];

void dfs(int x, int fa)
{
	d[pre[x] = ++t] = x;
	L[col[x]] = min(L[col[x]], t);
	R[col[x]] = max(R[col[x]], t);
	loop(i, ft[x], nt)
	{
		if (u[i] == fa) continue;
		dfs(u[i], x);
		U[Sz] = pre[u[i]];	V[Sz] = v[i];
		Nt[Sz] = Ft[t];	    Ft[t] = Sz++;
	}
}

void add(int x, int y)
{
	for (int i = x; i <= n; i += low(i)) f[i] += y;
}

int get(int x)
{
	int res = 0;
	for (int i = x; i; i -= low(i)) res += f[i];
	return res;
}

int main()
{
	while (scanf("%d", &n) != EOF)
	{
		Sz = sz = t = 0;
		rep(i, 1, n) in(col[i]);
		rep(i, 1, n) Ft[i] = ft[i] = -1;
		rep(i, 1, n) L[i] = n + 1, R[i] = f[i] = 0;
		rep(i, 1, n - 1)
		{
			in(x);	in(y);
			u[sz] = y; v[sz] = i; nt[sz] = ft[x];	ft[x] = sz++;
			u[sz] = x; v[sz] = i; nt[sz] = ft[y];	ft[y] = sz++;
		}
		dfs(1, 0);
		rep(i, 1, n) pre[i] = f[i] = 0;
		rep(i, 1, n)
		{
			add(pre[col[d[i]]] + 1, 1);	add(i + 1, -1); pre[col[d[i]]] = i;
			if (i == R[col[d[i]]]) add(1, -1), add(L[col[d[i]]] + 1, 1);
			loop(j, Ft[i], Nt) ans[V[j]] = get(U[j]);
		}
		rep(i, 1, n - 1) printf("%d\n", ans[i]);
	}
	return 0;
}

换一种角度思考,同样是树形态,如果我们可以统计出某个节点全部儿子的情况,那么将儿子的状态全部合并加上当前节点的颜色就可以得到答案。

然后问题是怎么快速的合并统计答案,这个时候可以用启发式合并,线段树和splay都可以轻松的完成这种操作。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define rep(i,j,k) for (int i = j; i <= k; i++)
#define per(i,j,k) for (int i = j; i >= k; i--)
#define loop(i,j,k) for (int i = j;i != -1; i = k[i])
#define lson x << 1, l, mid
#define rson x << 1 | 1, mid + 1, r
#define ff first
#define ss second
#define mp(i,j) make_pair(i,j)
#define pb push_back
#define pii pair
#define in(x) scanf("%d", &x);
using namespace std;
typedef long long LL;
const int low(int x) { return x&-x; }
const double eps = 1e-9;
const int INF = 0x7FFFFFFF;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
const int M = 2e6 + 10;
int n, x, y, col[N], cnt[N], ans[N], t[N];
int ft[N], nt[N], u[N], v[N], sz;
int L[M], R[M], f[M], g[M], tot;

int node()
{
	L[tot] = R[tot] = f[tot] = g[tot] = 0; return tot++;
}

void make(int &x, int l, int r, int u)
{
	if (!x) x = node();
	if (l == r) f[x] = g[x] = 1 % cnt[u];
	else
	{
		int mid = l + r >> 1;
		if (u <= mid) make(L[x], l, mid, u);
		else make(R[x], mid + 1, r, u);
		f[x] = f[L[x]] + f[R[x]];
	}
}

void merge(int &x, int y, int l, int r)
{
	if (!x || !y) { x = x^y; return; }
	if (l == r) f[x] = (bool)(g[x] = (g[x] + g[y]) % cnt[l]);
	else
	{
		int mid = l + r >> 1;
		merge(L[x], L[y], l, mid);
		merge(R[x], R[y], mid + 1, r);
		f[x] = f[L[x]] + f[R[x]];
	}
}

void dfs(int x, int fa)
{
	make(t[x] = 0, 1, n, col[x]);
	loop(i, ft[x], nt)
	{
		if (u[i] == fa) continue;
		dfs(u[i], x);
		ans[v[i]] = f[t[u[i]]];
		merge(t[x], t[u[i]], 1, n);
	}
}

int main()
{
	while (scanf("%d", &n) != EOF)
	{
		tot = sz = 0;	node();
		rep(i, 1, n) cnt[i] = 0, ft[i] = -1;
		rep(i, 1, n) { in(col[i]); cnt[col[i]]++; }
		rep(i, 1, n - 1)
		{
			in(x);	in(y);
			u[sz] = y; v[sz] = i; nt[sz] = ft[x];	ft[x] = sz++;
			u[sz] = x; v[sz] = i; nt[sz] = ft[y];	ft[y] = sz++;
		}
		dfs(1, 0);
		rep(i, 1, n - 1) printf("%d\n", ans[i]);
	}
	return 0;
}


Splay版,好久没写回忆一下,神奇的是试了一下,单纯的平衡树速度反而快一点,加了splay时间却增加了。

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define rep(i,j,k) for (int i = j; i <= k; i++)
#define per(i,j,k) for (int i = j; i >= k; i--)
#define loop(i,j,k) for (int i = j;i != -1; i = k[i])
#define lson x << 1, l, mid
#define rson x << 1 | 1, mid + 1, r
#define ff first
#define ss second
#define mp(i,j) make_pair(i,j)
#define pb push_back
#define pii pair
#define in(x) scanf("%d", &x);
using namespace std;
typedef long long LL;
const int low(int x) { return x&-x; }
const double eps = 1e-9;
const int INF = 0x7FFFFFFF;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
const int M = 2e6 + 10;
int n, x, y, col[N], cnt[N], ans[N], t[N];
int ft[N], nt[N], u[N], v[N], sz;

struct Splays
{
	int ch[M][2], F[M], sz;
	int A[M], B[M], C[M];
	int Node(int f, int c, int z) 
	{ 
		F[sz] = f; A[sz] = c; C[sz] = (bool)(B[sz] = z % cnt[c]); 
		ch[sz][0] = ch[sz][1] = 0; return sz++;
	}
	void clear() { sz = 1; ch[0][0] = ch[0][1] = F[0] = 0; C[0] = 0; }
	void rotate(int x, int k)
	{
		int y = F[x]; ch[y][!k] = ch[x][k]; F[ch[x][k]] = y;
		if (F[y]) ch[F[y]][y == ch[F[y]][1]] = x;
		F[x] = F[y];    F[y] = x;   ch[x][k] = y;
		C[x] = C[y];    C[y] = C[ch[y][0]] + C[ch[y][1]] + (bool)B[y];
	}
	void Splay(int x, int r)
	{
		while (F[x] != r)
		{
			if (F[F[x]] == r) { rotate(x, x == ch[F[x]][0]); return; }
			int y = (x == ch[F[x]][0]), z = (F[x] == ch[F[F[x]]][0]);
			y^z ? (rotate(x, y), rotate(x, z)) : (rotate(F[x], z), rotate(x, y));
		}
	}
	void insert(int &x, int fa, int y, int z)
	{
		if (!x) { x = Node(fa, y, z); return; }
		if (A[x] == y)
		{
			B[x] = (B[x] + z) % cnt[y];
			C[x] = C[ch[x][0]] + C[ch[x][1]] + (bool)B[x];
		}
		else
		{
			insert(ch[x][A[x] < y], x, y, z);
			C[x] = C[ch[x][0]] + C[ch[x][1]] + (bool)B[x];
		}
	}
	void get(int &x, int y)
	{
		for (int i = x;; i = ch[i][A[i] < y])
		{
			if (A[i] == y) { Splay(i, 0); x = i; break; }
		}
	}
	void merge(int &x, int y)
	{
		if (!y) return;
		if (B[y]) { insert(x, 0, A[y], B[y]); get(x, A[y]); }
		merge(x, ch[y][0]);
		merge(x, ch[y][1]);
	}
}solve;

void dfs(int x, int fa)
{
	solve.insert(t[x] = 0, 0, col[x], 1);
	solve.get(t[x], col[x]);
	loop(i, ft[x], nt)
	{
		if (u[i] == fa) continue;
		dfs(u[i], x);
		ans[v[i]] = solve.C[t[u[i]]];
		if (solve.C[t[u[i]]] > solve.C[t[x]])
		{
			solve.merge(t[u[i]], t[x]);
			t[x] = t[u[i]];
		}
		else solve.merge(t[x], t[u[i]]);
	}
}

int main()
{
	while (scanf("%d", &n) != EOF)
	{
		solve.clear(); sz = 0;
		rep(i, 1, n) cnt[i] = 0, ft[i] = -1;
		rep(i, 1, n) { in(col[i]); cnt[col[i]]++; }
		rep(i, 1, n - 1)
		{
			in(x);	in(y);
			u[sz] = y; v[sz] = i; nt[sz] = ft[x];	ft[x] = sz++;
			u[sz] = x; v[sz] = i; nt[sz] = ft[y];	ft[y] = sz++;
		}
		dfs(1, 0);
		rep(i, 1, n - 1) printf("%d\n", ans[i]);
	}
	return 0;
}


你可能感兴趣的:(CSU,----树状数组,----线段树,----Splay)