[Luogu P2178] [BZOJ 4199] [NOI2015]品酒大会

36 篇文章 1 订阅
20 篇文章 0 订阅
洛谷传送门
BZOJ传送门

题目描述

一年一度的“幻影阁夏日品酒大会”隆重开幕了。大会包含品尝和趣味挑战 两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加。

在大会的晚餐上,调酒师 Rainbow 调制了 n n n 杯鸡尾酒。这 n n n 杯鸡尾酒排成一行,其中第 n n n 杯酒 ( 1 ≤ i ≤ n ) (1 ≤ i ≤ n) (1in) 被贴上了一个标签 s i s_i si,每个标签都是 26 26 26 个小写 英文字母之一。设 s t r ( l , r ) str(l, r) str(l,r)表示第 l l l 杯酒到第 r r r 杯酒的 r − l + 1 r − l + 1 rl+1 个标签顺次连接构成的字符串。若 s t r ( p , p o ) = s t r ( q , q o ) str(p, po) = str(q, qo) str(p,po)=str(q,qo),其中 1 ≤ p ≤ p o ≤ n , 1 ≤ q ≤ q o ≤ n , p ≠ q , p o − p + 1 = q o − q + 1 = r 1 ≤ p ≤ po ≤ n, 1 ≤ q ≤ qo ≤ n, p ≠ q, po − p + 1 = qo − q + 1 = r 1ppon,1qqon,p̸=q,pop+1=qoq+1=r ,则称第 p p p 杯酒与第 q q q 杯酒是“ r r r 相似” 的。当然两杯“ r r r 相似”( r r r > 1)的酒同时也是“ 1 1 1 相似”、“ 2 2 2 相似”、……、“ ( r − 1 ) (r − 1) (r1) 相似”的。特别地,对于任意的 1 ≤ p , q ≤ n , p ≠ q 1 ≤ p , q ≤ n , p ≠ q 1p,qnp̸=q ,第 p p p 杯酒和第 $q $杯酒都 是“ 0 0 0 相似”的。

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

输入输出格式

输入格式:

1 1 1 行包含 1 1 1 个正整数 n n n ,表示鸡尾酒的杯数。

2 2 2 行包含一个长度为 n n n 的字符串 S,其中第 i i i 个字符表示第 i i i 杯酒的标签。

3 3 3 行包含 n n n 个整数,相邻整数之间用单个空格隔开,其中第 i i i 个整数表示第 i i i 杯酒的美味度 a i a_i ai

输出格式:

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

输入输出样例

输入样例#1:
10
ponoiiipoi
2 1 4 7 4 8 3 6 4 7
输出样例#1:
45 56
10 56
3 32
0 0
0 0
0 0
0 0
0 0
0 0
0 0
输入样例#2:
12
abaabaabaaba
1 -2 3 -4 5 -6 7 -8 9 -10 11 -12
输出样例#2:
66 120
34 120
15 55
12 40
9 27
7 16
5 7
3 -4
2 -4
1 -4
0 0
0 0

说明

【样例说明 1】

用二元组 ( p , q ) (p, q) (p,q) 表示第 p p p 杯酒与第 q q q 杯酒。

0 0 0 相似:所有 45 45 45 对二元组都是 0 0 0 相似的,美味度最大的是 8 × 7 = 56 8 × 7 = 56 8×7=56

1 1 1 相似: ( 1 , 8 ) ( 2 , 4 ) ( 2 , 9 ) ( 4 , 9 ) ( 5 , 6 ) ( 5 , 7 ) ( 5 , 10 ) ( 6 , 7 ) ( 6 , 10 ) ( 7 , 10 ) (1,8) (2,4) (2,9) (4,9) (5,6) (5,7) (5,10) (6,7) (6,10) (7,10) (1,8)(2,4)(2,9)(4,9)(5,6)(5,7)(5,10)(6,7)(6,10)(7,10) ,最大的 8 × 7 = 56 8 × 7 = 56 8×7=56

2 2 2 相似: ( 1 , 8 ) ( 4 , 9 ) ( 5 , 6 ) (1,8) (4,9) (5,6) (1,8)(4,9)(5,6) ,最大的 4 × 8 = 32 4 × 8 = 32 4×8=32

没有 3 , 4 , 5 , ⋯ , 9 3,4,5, ⋯ ,9 3,4,5,,9 相似的两杯酒,故均输出 0 0 0

img

解题分析

其实和这道题很像, 求的是有关后缀的前缀, 我们还是把整个串翻转过来, 插入 S A M SAM SAM, 就变成求有关前缀的公共后缀的问题。 这个直接在 p a r e n t parent parent树上从下往上 d p dp dp就好了。

注意最后输出方案数的时候需要从后往前累加, 因为我们 d p dp dp的时候只关注了越过子树根节点的方案数, 没有计算在子树内的方案数,因为统计答案的方式和点分治有点像。另外这道题的权值比较大, d p dp dp的初值需要设为 L O N G   L O N G   M A X LONG\ LONG\ MAX LONG LONG MAX

代码如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <limits.h>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define ll long long
#define MX 1500500
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
bool neg;
template <class T>
IN void in(T &x)
{
	x = 0; R char c = gc;
	for (; !isdigit(c); c = gc)
	if(c == '-') neg = true;
	for (;  isdigit(c); c = gc)
	x = (x << 1) + (x << 3) + c - 48;
	if(neg) neg = false, x = -x;
}
int to[MX][26], len[MX], par[MX], siz[MX], buc[MX], ind[MX];
ll tot[MX], mn[MX], mx[MX], ans[MX], val[MX];
char dat[MX];
int l, cur, last, cnt;
IN bool isok(R int now) {return mn[now] != LONG_LONG_MAX && mx[now] != -LONG_LONG_MAX;}
namespace SAM
{
	IN void insert(R int id, ll val)
	{
		R int now = last, tar;
		cur = ++cnt; len[cur] = len[last] + 1; siz[cur] = 1;
		last = cur; mx[cur] = mn[cur] = val;
		for (; (~now) && !to[now][id]; now = par[now]) to[now][id] = cur;
		if(now < 0) return par[cur] = 0, void();
		tar = to[now][id];
		if(len[tar] == len[now] + 1) return par[cur] = tar, void();
		int nw = ++cnt; len[nw] = len[now] + 1;
		par[nw] = par[tar], par[tar] = par[cur] = nw;
		std::memcpy(to[nw], to[tar], sizeof(to[nw]));
		for (; (~now) && to[now][id] == tar; now = par[now]) to[now][id] = nw;
	}
}
int main(void)
{
	in(l); par[0] = -1; R int now, fa;
	scanf("%s", dat + 1); std::memset(ans, -63, sizeof(ans));
	for (R int i = 1; i <= l; ++i) in(val[i]);
	std::reverse(dat + 1, dat + 1 + l);
	std::reverse(val + 1, val + 1 + l);
	for (R int i = 1; i <= l; ++i) SAM::insert(dat[i] - 'a', val[i]);
	for (R int i = 1; i <= cnt; ++i) buc[len[i]]++;
	for (R int i = 1; i <= cnt; ++i) buc[i] += buc[i - 1];
	for (R int i = 1; i <= cnt; ++i) ind[buc[len[i]]--] = i;
	for (R int i = cnt; i; --i)
	{
			now = ind[i]; fa = par[now];
			if(!mn[fa] && !mx[fa]) mn[fa] = LONG_LONG_MAX, mx[fa] = -LONG_LONG_MAX;
			if(isok(now) && isok(fa)) ans[len[fa]] = max(ans[len[fa]], max(mn[fa] * mn[now], mx[fa] * mx[now]));
			mn[fa] = min(mn[fa], mn[now]);
			mx[fa] = max(mx[fa], mx[now]);
			tot[len[fa]] += 1ll * siz[fa] * siz[now], siz[fa] += siz[now];
	}
	for (R int i = l - 1; ~i; --i)
	{
		tot[i] += tot[i + 1];
		ans[i] = max(ans[i], ans[i + 1]);
	}
	for (R int i = 0; i < l; ++i)
	{
		if(tot[i]) printf("%lld %lld\n", tot[i], ans[i]);
		else puts("0 0");
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值