[ZPG TEST 114] 括号匹配【树分治 点分治 括号序列】

  1. 1.      括号匹配

 

有一棵树,每个节点上都有一个括号(左括号或者右括号)。有多少个有序点对(u, v)从u到v的路径上的节点构成的字符串是一个合法的括号匹配?(我们称这样的点对是合法的)

输入格式

       第一行是一个整数n,表示节点个数

       第二行是一个字符串,由(和)组成,第i个字符表示第i个节点的括号。

       接下来n-1行,每行两个整数u, v 表示u与v之间有一条边。

输出格式

       输出一个整数,合法的点对数量。

输入样例

输出样例

4

()()

1 2

2 3

3 4

4

数据范围和约定

       30%的数据:n <= 2000

       100%的数据:n <= 100000

困扰了我多年(其实是多个月),终于于今日解决了!(我才不会说克罗地亚国赛最后一题是原题呢= =)如果没做过树分治的话还是先做一下poj1741感受一下叭。

先把随题配送的题解贴出来:

“这道题是很常规的树分治算法。分治之后考虑经过根的所有链,将链上的所有能匹配的括号合并,那么只有其中一边由若干左括号和另外一边的若干右括号的情况能构成合法的括号匹配。统计括号数即可。”

写的非常简洁,然而最初根本看不懂。事实上是这样子的,首先考虑当前子树,一条合法的括号序列链可以经过该子树的根节点,也可以不经过,对于后者可以递归求解,重点是看前者。

一条链经过这个根节点,那么这条链一定是长成这个样子的:

        根

        /

      /    

    /        

  /            

姑且把根旁边的两条支叫做左支与右支(注意原树并不一定是二叉树),那么这个括号序列一定是由左支的一些已经匹配好的括号序列,与右支的一些已经匹配好的括号序列,与左支还未匹配的 '(' 与右支未匹配的 ')' 。当然也有可能是由左支的一些已经匹配好的括号序列,与右支的一些已经匹配好的括号序列,与右支还未匹配的 '(' 与左支未匹配的 ')' 。这里不妨考虑前者。现在关键就是让那些分在两支的未匹配的括号得以匹配。具体的话还是看代码,最关键的是get_data()函数。还需要小注意一下78行的注释内容。

#include <cstdio>
#include <cstring>
#include <algorithm>

const int maxn = 100005, LEFT = 0, RIGHT = 1;

int n, head[maxn], to[maxn << 1], next[maxn << 1], lb, a[maxn], mp[128];
int t1, t2, siz[maxn], cnt[2][maxn], avail_len[2][maxn], idx[2], sum[2][maxn];
long long ans;
char ch, book[maxn];

inline void ist(int aa, int ss) {
	to[lb] = ss;
	next[lb] = head[aa];
	head[aa] = lb;
	++lb;
}
void get_siz(int r, int p) {
	siz[r] = 1;
	for (int j = head[r]; j != -1; j = next[j]) {
		if (!book[to[j]] && to[j] != p) {
			get_siz(to[j], r);
			siz[r] += siz[to[j]];
		}
	}
}
void fnd_zx(int r, int tot_node, int p, int & real_r, int & mn) {
	int mx = 0;
	for (int j = head[r]; j != -1; j = next[j]) {
		if (!book[to[j]] && to[j] != p) {
			fnd_zx(to[j], tot_node, r, real_r, mn);
			mx = std::max(mx, siz[to[j]]);
		}
	}
	mx = std::max(mx, tot_node - siz[r]);
	if (mn > mx) {
		mn = mx;
		real_r = r;
	}
}
void get_data(int r, int p, int dir, int avail, int s) {
	// r是当前子树的根节点,p是其父亲,dir是你想要找的可供与另一支匹配的括号类型,
	// avail表示目前可供匹配的个数,s有点玄 ,只有当s为零的时候,这个括号序列才是
	// 可以用来与另一支匹配的,用语言不好表达,简单模拟一下程序的45~61行就懂了 
	if (a[r] == dir) {
		if (s) {
			--s;
		}
		else {
			++avail;
		}
	}
	else {
		++s;
	}
	if (!s) {
		if (!cnt[dir][avail]) {
			avail_len[dir][idx[dir]++] = avail;
		}
		++cnt[dir][avail];
	}
	for (int j = head[r]; j != -1; j = next[j]) {
		if (!book[to[j]] && to[j] != p) {
			get_data(to[j], r, dir, avail, s);
		}
	}
}
void slove(int fake_r) {
	int real_r = -666, mn = 2147483647, tem;
	get_siz(fake_r, 0);
	fnd_zx(fake_r, siz[fake_r], 0, real_r, mn);
	book[real_r] = 1;
	for (int j = head[real_r]; j != -1; j = next[j]) {
		if (!book[to[j]]) {
			slove(to[j]);
		}
	}
	int mx_avail = 1;	// mx_avail must be initialized to 1! 0 is NOT OK!
	for (int j = head[real_r]; j != -1; j = next[j]) {
		if (!book[to[j]]) {
			if (a[real_r] == LEFT) {
				get_data(to[j], real_r, LEFT, 1, 0);
				get_data(to[j], real_r, RIGHT, 0, 0);
			}
			else {
				get_data(to[j], real_r, RIGHT, 1, 0);
				get_data(to[j], real_r, LEFT, 0, 0);
			}
			for (int dir = 0; dir < 2; ++dir) {
				for (int k = 0; k < idx[dir]; ++k) {
					tem = avail_len[dir][k];
					mx_avail = std::max(mx_avail, tem);
					ans -= (long long)cnt[dir][tem] * cnt[dir ^ 1][tem];
					sum[dir][tem] += cnt[dir][tem];
					cnt[dir][tem] = 0;
				}
				idx[dir] = 0;
			}
		}
	}
	
	++sum[a[real_r]][1];
	for (int i = 0; i <= mx_avail; ++i) {
		ans += (long long)sum[0][i] * sum[1][i];
		sum[0][i] = 0;
		sum[1][i] = 0;
	}
	
	book[real_r] = 0;
}

int main(void) {
	freopen("bracket.in", "r", stdin);
	freopen("bracket.out", "w", stdout);
	memset(head, -1, sizeof head);
	memset(next, -1, sizeof next);
	mp['('] = LEFT;
	mp[')'] = RIGHT;
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		while ((ch = getchar()) < '(');
		a[i] = mp[(int)ch];
	}
	for (int i = 1; i < n; ++i) {
		scanf("%d%d", &t1, &t2);
		ist(t1, t2);
		ist(t2, t1);
	}
	
	slove(1);
	printf("%I64d
", ans);
	return 0;
}

  

原文地址:https://www.cnblogs.com/ciao-sora/p/6628692.html