[BZOJ]3926 [ZJOI2015] 诸神眷顾的幻想乡 广义后缀自动机

3926: [Zjoi2015]诸神眷顾的幻想乡

Time Limit: 10 Sec   Memory Limit: 512 MB
Submit: 1631   Solved: 947
[ Submit][ Status][ Discuss]

Description

 幽香是全幻想乡里最受人欢迎的萌妹子,这天,是幽香的2600岁生日,无数幽香的粉丝到了幽香家门前的太阳花田上来为幽香庆祝生日。 

粉丝们非常热情,自发组织表演了一系列节目给幽香看。幽香当然也非常高兴啦。 
这时幽香发现了一件非常有趣的事情,太阳花田有n块空地。在过去,幽香为了方便,在这n块空地之间修建了n-1条边将它们连通起来。也就是说,这n块空地形成了一个树的结构。 
有n个粉丝们来到了太阳花田上。为了表达对幽香生日的祝贺,他们选择了c中颜色的衣服,每种颜色恰好可以用一个0到c-1之间的整数来表示。并且每个人都站在一个空地上,每个空地上也只有一个人。这样整个太阳花田就花花绿绿了。幽香看到了,感觉也非常开心。 
粉丝们策划的一个节目是这样的,选中两个粉丝A和B(A和B可以相同),然后A所在的空地到B所在的空地的路径上的粉丝依次跳起来(包括端点),幽香就能看到一个长度为A到B之间路径上的所有粉丝的数目(包括A和B)的颜色序列。一开始大家打算让人一两个粉丝(注意:A,B和B,A是不同的,他们形成的序列刚好相反,比如红绿蓝和蓝绿红)都来一次,但是有人指出这样可能会出现一些一模一样的颜色序列,会导致审美疲劳。 
于是他们想要问题,在这个树上,一共有多少可能的不同的颜色序列(子串)幽香可以看到呢? 
太阳花田的结构比较特殊,只与一个空地相邻的空地数量不超过20个。 

Input

 第一行两个正整数n,c。表示空地数量和颜色数量。 

第二行有n个0到c-1之间,由空格隔开的整数,依次表示第i块空地上的粉丝的衣服颜色。(这里我们按照节点标号从小到大的顺序依次给出每块空地上粉丝的衣服颜色)。 
接下来n-1行,每行两个正整数u,v,表示有一条连接空地u和空地v的边。 

Output

 一行,输出一个整数,表示答案。 

Sample Input

7 3
0 2 1 2 1 0 0
1 2
3 4
3 5
4 6
5 7
2 5

Sample Output

30

HINT

对于所有数据,1<=n<=100000, 1<=c<=10。 


对于15%的数据,n<=2000。 

另有5%的数据,所有空地都至多与两个空地相邻。 

另有5%的数据,除一块空地与三个空地相邻外,其他空地都分别至多与两个空地相邻。 

另有5%的数据,除某两块空地与三个空地相邻外,其他空地都分别至多与两个空地相邻

Source

[ Submit][ Status][ Discuss]


HOME Back

  所以说标题这么霸气跟内容有什么关系咩?

  题目大意就是统计树上不同串的个数... 发现我只会做一个串的于是默默打开了题解... 广义后缀自动机? 感觉什么东西加了广义都变得高大上了? 实际上广义后缀自动机就是处理多个串的情况.

  首先一个比较显然(但是没想到啊)的结论就是原树上的任意一个子串一定会直线(深度单调递增)存在于某个原树里的叶子节点提起来变成的树里. 深度单调可以干嘛? 我们可以发现题目中给出的实际上就是一个trie, 然而我们建出一个trie的后缀自动机可以得到这个trie树里任意一个深度单调递增的子串(这个好像就已经叫广义后缀自动机了?). 那么把每个叶子节点作为根的trie都插入后缀自动机里就可以了... 这样一定可以在这个后缀自动机里表示出原树里的每个子串. 实际实现过程中, 一个trie的u节点插入的时候,last就是trie树上的父节点(正确性显然?). 每一个trie刚插进来的时候都从root开始. 这道题由于叶子结点最多就20个, 所以复杂度不会爆棚.

  统计答案就很naive了, 跟统计普通串一样的. 根据parent树上的性质, 统计len[i] - len[par[i]].

#include<bits/stdc++.h>
using namespace std;
typedef long long lnt;
const int maxm = 1e5 + 5;
const int maxn = 4e6 + 5;
lnt ans;
int n, cc, tot, num, root;
int a[maxn][10], h[maxm], dep[maxn], par[maxn], in[maxm], col[maxm];
struct edge { int nxt, v; }e[maxm << 1];
inline const int read() {
	register int x = 0;
	register char ch = getchar();
	while (ch < '0' || ch > '9') ch = getchar();
	while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
	return x;
}
inline void add(int u, int v) {
	in[u] ++, in[v] ++;
	e[++ num].v = v, e[num].nxt = h[u], h[u] = num;
	e[++ num].v = u, e[num].nxt = h[v], h[v] = num;
}
inline int insert(int p, int c) {
	int np = ++ tot;
	dep[np] = dep[p] + 1;
	while (p && !a[p][c]) a[p][c] = np, p = par[p];
	if (!p) par[np] = root;
	else {
		int q = a[p][c];
		if (dep[q] == dep[p] + 1) par[np] = q;
		else {
			int nq = ++ tot;
			par[nq] = par[q];
			dep[nq] = dep[p] + 1;
			memcpy(a[nq], a[q], sizeof(a[q]));
			par[np] = par[q] = nq;
			while (a[p][c] == q) a[p][c] = nq, p = par[p];
		}
	}
	return np;
}
void dfs(int u, int fa, int pre) {
	int now = insert(pre, col[u]);
	for (int i = h[u]; i; i = e[i].nxt) 
		if (e[i].v != fa) dfs(e[i].v, u, now);
}
int main() {
	int u, v;
	root = ++ tot;
	n = read(), cc = read();
	for (int i = 1; i <= n; ++ i) col[i] = read();
	for (int i = 1; i < n; ++ i) 
		u = read(), v = read(), add(u, v);
	for (int i = 1; i <= n; ++ i)
		if (in[i] == 1) dfs(i, 0, 1);
	for (int i = 2; i <= tot; ++ i)
		ans += (lnt)dep[i] - dep[par[i]];
	printf("%lld\n", ans);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值