【bzoj4199】[Noi2015]品酒大会 后缀自动机求后缀树+树形dp

题目描述(转自百度文库)

一年一度的“幻影阁夏日品酒大会”隆重开幕了。大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加。 
在大会的晚餐上,调酒师Rainbow调制了 𝑛 杯鸡尾酒。这 n 杯鸡尾酒排成一行,其中第 𝑖 杯酒 (1≤𝑖≤𝑛) 被贴上了一个标签 𝑠𝑖 ,每个标签都是 26 个小写英文字母之一。设 𝑆𝑡𝑟(𝑙,𝑟)表示第 𝑙 杯酒到第 𝑟 杯酒的 𝑟−𝑙+1 个标签顺次连接构成的字符串。若 𝑆𝑡𝑟(𝑝,𝑝𝑜)=𝑆𝑡𝑟(𝑞,𝑞𝑜),其中 1≤𝑝≤𝑝𝑜≤𝑛,1≤𝑞≤𝑞𝑜≤𝑛,𝑝≠𝑞,𝑝𝑜−𝑝+1=𝑞𝑜−𝑞+1=𝑟 ,则称第 𝑝 杯酒与第 𝑞 杯酒是“ 𝑟 相似”
的。当然两杯“ 𝑟 相似”(𝑟>1)的酒同时也是“ 1 相似”、“ 2 相似”、……、“ (𝑟−1)相似”的。特别地,对于任意的 1≤𝑝,𝑞≤𝑛 , 𝑝≠𝑞 ,第 𝑝 杯酒和第 𝑞 杯酒都是“ 0 相似”的。 

在品尝环节上,品酒师Freda轻松地评定了每一杯酒的美味度,凭借其专业的水准和经验成功夺取了“首席品酒家”的称号,其中第 𝑖 杯酒 (1≤𝑖≤𝑛) 的美味度为 𝑎𝑖 。现在Rainbow公布了挑战环节的问题:本次大会调制的鸡尾酒有一个特点,如果把第 𝑝 杯酒与第 𝑞 杯酒调兑在一起,将得到一杯美味度为 𝑎𝑝𝑎𝑞 的酒。现在请各位品酒师分别对于 𝑟=0,1,2,⋯,𝑛−1 ,统计出有多少种方法可以选出 2 杯“ 𝑟 相似”的酒,并回答选择 2 杯“ 𝑟 相似”的酒调兑可以得到的美味度的最大值。

输入

输入文件的第1行包含1个正整数 𝑛 ,表示鸡尾酒的杯数。 
第 2 行包含一个长度为 𝑛 的字符串 𝑆 ,其中第 𝑖 个字符表示第 𝑖 杯酒的标签。第 3 行包含 𝑛 个整数,相邻整数之间用单个空格隔开,其中第 𝑖 个整数表示第 𝑖 杯酒的美味度 𝑎𝑖 。

输出
输出文件包括 𝑛 行。第 𝑖 行输出 2 个整数,中间用单个空格隔开。第 1 个整数表示选出两杯“ (𝑖−1) 相似”的酒的方案数,第 2 个整数表示选出两杯“(𝑖−1) 相似”的酒调兑可以得到的最大美味度。若不存在两杯“ (𝑖−1) 相似”的酒,这两个数均为 0 。

样例输入

10 

ponoiiipoi

2 1 4 7 4 8 3 6 4 7 

样例输出

45 56 

10 56

3 32 

0 0 

0 0 

0 0 

0 0 

0 0 

0 0 

0 0


题解

后缀自动机求后缀树+树形dp

首先要知道后缀自动机的一个性质:后缀自动机的parent树是反串的后缀树,也即反串的后缀自动机的parent树是后缀树。

所以我们可以把原串反过来加入到后缀自动机中,求出原串的后缀树。

而后缀树是一棵压缩后的Trie树,Trie树又有性质:两个节点lca的深度(字符串长度)是两个串的公共前缀长度。

根据这个我们可以树形dp。

设ans1[x]、ans2[x]表示对应最大相似为x时的两种答案。

那么当枚举到深度为x的点i时,应将其自身与子树的两两组合统计到ans1中,可以在每次si[i]+=si[son]时更新答案。

而由于有负数的存在,ans2的情况比较复杂。考虑可能成为答案的只有 最大值×次大值 和 最小值×次小值 ,同样在maxn=max(maxn[i],maxn[son])时更新答案。

最后,我们求出的时最大相似,也即最长公共前缀,实际上小于等于这个值都可以。所以我们还需要求一下后缀和以及后缀最大值,具体见代码。

最后的最后,必须要吐槽一下:博客园的编辑器太辣鸡了,随便粘贴一点东西要调好长时间的格式(这也是我没有从uoj粘贴题目的原因,因为博客园粘贴latex会出错),这点真的好气啊QAQ

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 600010
#define MAX 0x7f7f7f7f
#define MIN 0x80808080
using namespace std;
int w[N] , a[N][26] , fa[N] , dis[N] , last = 1 , tot = 1 , head[N] , to[N] , next[N] , cnt , si[N] , maxn[N] , minn[N];
long long ans1[N] , ans2[N];
char str[N];
void add(int x , int y)
{
	to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void ins(int c , int v)
{
	int p = last , np = last = ++tot;
	dis[np] = dis[p] + 1 , maxn[np] = minn[np] = v , si[np] = 1;
	while(p && !a[p][c]) a[p][c] = np , p = fa[p];
	if(!p) fa[np] = 1;
	else
	{
		int q = a[p][c];
		if(dis[q] == dis[p] + 1) fa[np] = q;
		else
		{
			int nq = ++tot;
			memcpy(a[nq] , a[q] , sizeof(a[q])) , dis[nq] = dis[p] + 1 , fa[nq] = fa[q] , fa[np] = fa[q] = nq;
			while(p && a[p][c] == q) a[p][c] = nq , p = fa[p];
		}
	}
}
void dfs(int x)
{
	int i , p = dis[x];
	for(i = head[x] ; i ; i = next[i])
	{
		dfs(to[i]);
		ans1[p] += (long long)si[x] * si[to[i]];
		if(maxn[x] != MIN) ans2[p] = max(ans2[p] , (long long)maxn[x] * maxn[to[i]]);
		if(minn[x] != MAX) ans2[p] = max(ans2[p] , (long long)minn[x] * minn[to[i]]);
		si[x] += si[to[i]] , maxn[x] = max(maxn[x] , maxn[to[i]]) , minn[x] = min(minn[x] , minn[to[i]]);
	}
}
int main()
{
	int n , i;
	scanf("%d%s" , &n , str + 1);
	memset(maxn , 0x80 , sizeof(maxn)) , memset(minn , 0x7f , sizeof(minn)) , memset(ans2 , 0x80 , sizeof(ans2));
	for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &w[i]);
	for(i = n ; i >= 1 ; i -- ) ins(str[i] - 'a' , w[i]);
	for(i = 2 ; i <= tot ; i ++ ) add(fa[i] , i);
	dfs(1);
	for(i = n - 2 ; i >= 0 ; i -- ) ans1[i] += ans1[i + 1] , ans2[i] = max(ans2[i] , ans2[i + 1]);
	for(i = 0 ; i < n ; i ++ ) printf("%lld %lld\n" , ans1[i] , ans1[i] ? ans2[i] : 0);
	return 0;
}

 

 

posted @ 2017-06-06 13:58  GXZlegend  阅读(472)  评论(0编辑  收藏  举报