讲道理是后缀数组裸题吧,虽然知道后缀数组的原理但是小C不会写是什么鬼。。
小C趁着做这题的当儿,学习了一下后缀数组。
网络上的后缀数组模板完全看不懂怎么破,全程照着黄学长的代码抄,感觉黄学长写得还是很优雅的。
求LCP的部分已经崩坏了,小C自己脑补的做法是。。倍增??
看到正确的写法之后小C内心是绝望的,大致意思是:在原字符串中,设相邻两个后缀为Sx、Sx+1,那么有height[x+1]>=height[x]-1。(height数组就是小C代码里的tp数组)
Description
一年一度的“幻影阁夏日品酒大会”隆重开幕了。大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加。在大会的晚餐上,调酒师 Rainbow 调制了 n 杯鸡尾酒。这 n 杯鸡尾酒排成一行,其中第 n 杯酒 (1 ≤ i ≤ n) 被贴上了一个标签si,每个标签都是 26 个小写 英文字母之一。设 str(l, r)表示第 l 杯酒到第 r 杯酒的 r − l + 1 个标签顺次连接构成的字符串。若 str(p, po) = str(q, qo),其中 1 ≤ p ≤ po ≤ n, 1 ≤ q ≤ qo ≤ n, p ≠ q, po − p + 1 = qo − q + 1 = r ,则称第 p 杯酒与第 q 杯酒是“ r 相似” 的。当然两杯“ r 相似”(r > 1)的酒同时也是“ 1 相似”、“ 2 相似”、……、“ (r − 1) 相似”的。特别地,对于任意的 1 ≤ p , q ≤ n , p ≠ q ,第 p 杯酒和第 q 杯酒都 是“ 0 相似”的。
在品尝环节上,品酒师 Freda 轻松地评定了每一杯酒的美味度,凭借其专业的水准和经验成功夺取了“首席品酒家”的称号,其中第 i 杯酒 (1 ≤ i ≤ n) 的 美味度为 ai 。现在 Rainbow 公布了挑战环节的问题:本次大会调制的鸡尾酒有一个特点,如果把第 p 杯酒与第 q 杯酒调兑在一起,将得到一杯美味度为 ap*aq 的 酒。现在请各位品酒师分别对于 r = 0,1,2, ⋯ , n − 1 ,统计出有多少种方法可以 选出 2 杯“ r 相似”的酒,并回答选择 2 杯“ r 相似”的酒调兑可以得到的美味度的最大值。
Input
第 1 行包含 1 个正整数 n ,表示鸡尾酒的杯数。
第 2 行包含一个长度为 n 的字符串 S,其中第 i 个字符表示第 i 杯酒的标签。
第 3 行包含 n 个整数,相邻整数之间用单个空格隔开,其中第 i 个整数表示第 i 杯酒的美味度 ai 。
Output
Sample Input
12
abaabaabaaba
1 -2 3 -4 5 -6 7 -8 9 -10 11 -12
Sample Output
66 120
34 120
15 55
12 40
9 27
7 16
5 7
3 -4
2 -4
1 -4
0 0
0 0
HINT
Solution
这种字符串子串比较的题目,应该很容易都能想到后缀数组。
我们发现如果两杯酒是k相似的,那么k一定小等于 以这两杯酒为开头的后缀 的最长公共前缀。
我们将原串转化为排序好的后缀数组(原串之后就没用了)显然在后缀数组上相邻的最长公共前缀就是它们之间的height。
进一步说,在后缀数组上任意两个不相等的后缀s[l]、s[r],那么它们的最长公共前缀就是。
这样我们得到了n-1个height,设height[i]=height(s[i-1],s[i])。
height[i]对答案的贡献是存在最长的区间[l,r],使得,如果height有相同的情况,强制给他们定义大小级别。
小C举个例子:
上图中,height[7]影响答案的范围是[6,10],这样左框里的元素3、7,都可以分别和右框里的1、2、4、9配对,每个配对之间的最长公共前缀一定是1。
(顺带一提,a数组就是题目中所述a数组,小C在此提醒方便读者阅读。)
因此,height[7]对于为1相似的配对的数目ans1[1]的贡献是2*4=8,对于为1相似的配对的最大美味度ans2[1]的贡献是7*9=63。
如果像上图中height[7]影响了[6,10],那么同样值等于1的height[9]影响的范围最多只能是[8,10]了,因为我们强制给它们定义了大小级别。
统计最大值时要注意负负得正的情况,所以要同时记录区间的最大值和最小值。
以上就基本解决了统计答案一类的问题,那么如何寻找每个height影响的区间呢?
很容易想到的就是对于每个height分别向左向右利用ST表二分它作为最小值的区间,注意值相等的情况,复杂度O(nlogn)。
还有就是解决这类问题很经典的区间RMQ数据结构——笛卡尔树,可以实现O(n)解决问题。
简单的说,笛卡尔树长得就像一棵treap,它既满足二分查找树的性质,又满足堆的性质。(假装大家都会treap)
大约就可以脑补笛卡尔树是干什么用的了吧?
如果还是不理解或是想知道构建方法的可以自行上网百度,小C这里就不多说了。
建树的时候把每个height看做一个点(本来height很像是两点之间的线段,这里就很像是化边为点?)。
而存储信息的时候每个节点存储的是后缀数组上的某段区间的信息,注意别混淆了。具体实现可以看小C的代码。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <ctime>
#define l(a) (son[a][0])
#define r(a) (son[a][1])
#define ll long long
#define INF 0x3FFFFFFF
#define MN 300005
#define MS 20
using namespace std;
int mp[MN],a[MN],sa[MS][MN],rak[MS][MN];
ll ad[MN],an[MN],FINF;
int son[MN][],fa[MN],tp[MN],st[MN],top;
int len[MN],w[MN],mn[MN],mx[MN];
char c[MN];
int n,p;
int T; inline int read()
{
int n=,f=; char c=getchar();
while (c<'' || c>'') {if(c=='-')f=-; c=getchar();}
while (c>='' && c<='') {n=n*+c-''; c=getchar();}
return n*f;
} void mul(int* osa,int* ork,int* nsa,int* nrk,int k)
{
register int i;
for (i=;i<=n;++i) mp[ork[osa[i]]]=i;
for (i=n;i;--i)
if (osa[i]>k) nsa[mp[ork[osa[i]-k]]--]=osa[i]-k;
for (i=n-k+;i<=n;++i) nsa[mp[ork[i]]--]=i;
for (i=;i<=n;++i)
nrk[nsa[i]]=nrk[nsa[i-]]+(ork[nsa[i]]!=ork[nsa[i-]]||ork[nsa[i]+k]!=ork[nsa[i-]+k]);
} void presa()
{
register int i,k;
for (i=;i<=n;++i) ++mp[a[i]];
for (i=;i<=;++i) mp[i]+=mp[i-];
for (i=;i<=n;++i) sa[p][mp[a[i]]--]=i;
for (i=;i<=n;++i) rak[p][sa[p][i]]=rak[p][sa[p][i-]]+(a[sa[p][i-]]!=a[sa[p][i]]);
for (k=;k<n;k<<=,++p) mul(sa[p],rak[p],sa[p+],rak[p+],k);
} void dfs(int x)
{
if (!x) return;
dfs(l(x)); dfs(r(x));
if (!l(x)) l(x)=MN-,mn[l(x)]=mx[l(x)]=w[sa[p][rak[p][x]-]],len[l(x)]=;
if (!r(x)) r(x)=MN-,mn[r(x)]=mx[r(x)]=w[sa[p][rak[p][x] ]],len[r(x)]=;
mn[x]=min(mn[l(x)],mn[r(x)]);
mx[x]=max(mx[l(x)],mx[r(x)]);
len[x]=len[l(x)]+len[r(x)];
ad[tp[x]]+=1LL*len[l(x)]*len[r(x)];
an[tp[x]]=max(an[tp[x]],1LL*mn[l(x)]*mn[r(x)]);
an[tp[x]]=max(an[tp[x]],1LL*mn[l(x)]*mx[r(x)]);
an[tp[x]]=max(an[tp[x]],1LL*mx[l(x)]*mn[r(x)]);
an[tp[x]]=max(an[tp[x]],1LL*mx[l(x)]*mx[r(x)]);
} int main()
{
register int i,k,x,y;
n=read();
scanf("%s",c+);
for (i=;i<=n;++i) a[i]=c[i]-'a'+;
presa();
for (i=;i<=n;++i) w[i]=read();
mn[]=INF; mx[]=-INF;
memset(an,,sizeof(an)); FINF=an[]; tp[]=-;
for (i=;i<=n;++i)
for (tp[sa[p][i]]=,k=p,x=sa[p][i],y=sa[p][i-];k>=;--k)
if (rak[k][x]==rak[k][y]) tp[sa[p][i]]+=(<<k),x+=(<<k),y+=(<<k);
for (i=;i<=n;++i)
{
for (x=;top&&tp[sa[p][i]]<=tp[st[top]];x=st[top--]);
if (x) fa[x]=sa[p][i];
l(sa[p][i])=x; r(st[top])=sa[p][i]; fa[sa[p][i]]=st[top]; st[++top]=sa[p][i];
}
dfs(st[]);
for (i=n-;i>=;--i) an[i]=max(an[i],an[i+]),ad[i]+=ad[i+];
for (i=;i<n;++i) printf("%lld %lld\n",ad[i],an[i]==FINF?:an[i]);
}
这个做法是小C之后才知道的,小C在这之前还自己YY了一个奇怪的线段树做法,思路好像完全不一样,洛谷上卡过了,BZOJ(时限0.5s)和自家OJ(未开O2)死活卡过不去几个点。代码姑且放出来留念吧。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define l(a) (a<<1)
#define r(a) (a<<1|1)
#define ll long long
#define MM 1200005
#define MN 300005
#define MS 20
using namespace std;
int mp[MN],a[MN],sa[MS][MN],rak[MS][MN];
ll ad[MM],an[MM],FINF;
int len[MM],w[MN],mn[MM],mx[MM],inf,Finf;
bool tg[MM];
char c[MN];
int n,p; inline int read()
{
int n=,f=; char c=getchar();
while (c<'' || c>'') {if(c=='-')f=-; c=getchar();}
while (c>='' && c<='') {n=n*+c-''; c=getchar();}
return n*f;
} void mul(int* osa,int* ork,int* nsa,int* nrk,int k)
{
register int i;
for (i=;i<=n;++i) mp[ork[osa[i]]]=i;
for (i=n;i;--i)
if (osa[i]>k) nsa[mp[ork[osa[i]-k]]--]=osa[i]-k;
for (i=n-k+;i<=n;++i) nsa[mp[ork[i]]--]=i;
for (i=;i<=n;++i)
nrk[nsa[i]]=nrk[nsa[i-]]+(ork[nsa[i]]!=ork[nsa[i-]]||ork[nsa[i]+k]!=ork[nsa[i-]+k]);
} void presa()
{
register int i,k;
for (i=;i<=n;++i) ++mp[a[i]];
for (i=;i<=;++i) mp[i]+=mp[i-];
for (i=;i<=n;++i) sa[p][mp[a[i]]--]=i;
for (i=;i<=n;++i) rak[p][sa[p][i]]=rak[p][sa[p][i-]]+(a[sa[p][i-]]!=a[sa[p][i]]);
for (k=;k<n;k<<=,++p) mul(sa[p],rak[p],sa[p+],rak[p+],k);
} void mark0(int x)
{
void down(int);
if (tg[x]) {ad[x]+=1LL*len[x]*(len[x]-)/; len[x]=; mn[x]=inf; mx[x]=Finf;}
else {down(x); tg[x]=;}
}
void enlen(int x,int z) {len[x]+=z;}
void upmx(int x,int z)
{
if (mx[x]!=Finf) an[x]=max(an[x],1LL*mx[x]*z);
if (mn[x]!=inf) an[x]=max(an[x],1LL*mn[x]*z);
mn[x]=min(mn[x],z); mx[x]=max(mx[x],z);
}
void down(int x)
{
if (tg[x]) {mark0(l(x)); mark0(r(x)); tg[x]=;}
if (len[x])
{
enlen(l(x),len[x]); enlen(r(x),len[x]);
upmx(l(x),mx[x]); upmx(r(x),mx[x]);
if (len[x]>) {upmx(l(x),mn[x]); upmx(r(x),mn[x]);}
len[x]=; mn[x]=inf; mx[x]=Finf;
}
if (ad[x]) {ad[l(x)]+=ad[x]; ad[r(x)]+=ad[x]; ad[x]=;}
if (an[x]!=FINF) {an[l(x)]=max(an[l(x)],an[x]); an[r(x)]=max(an[r(x)],an[x]); an[x]=FINF;}
} void write(int x,int L,int R)
{
if (L==R) {if (L<n) printf("%lld %lld\n",ad[x],an[x]==FINF?:an[x]); return;}
down(x);
int mid=L+R>>;
write(l(x),L,mid); write(r(x),mid+,R);
} void chzero(int x,int L,int R,int ql,int qr)
{
if (ql==L&&qr==R) {mark0(x); return;}
down(x);
int mid=L+R>>;
if (qr<=mid) chzero(l(x),L,mid,ql,qr);
else if (ql>mid) chzero(r(x),mid+,R,ql,qr);
else {chzero(l(x),L,mid,ql,mid); chzero(r(x),mid+,R,mid+,qr);}
} void modify(int x,int L,int R,int ql,int qr,int z)
{
if (ql==L&&qr==R) {enlen(x,); upmx(x,z); return;}
down(x);
int mid=L+R>>;
if (qr<=mid) modify(l(x),L,mid,ql,qr,z);
else if (ql>mid) modify(r(x),mid+,R,ql,qr,z);
else {modify(l(x),L,mid,ql,mid,z); modify(r(x),mid+,R,mid+,qr,z);}
} int main()
{
register int i,tp,k,x,y;
n=read();
scanf("%s",c+);
for (i=;i<=n;++i) a[i]=c[i]-'a'+;
for (i=;i<=n;++i) w[i]=read();
presa();
memset(an,,sizeof(an)); FINF=an[];
memset(mn,,sizeof(mn)); inf=mn[];
memset(mx,,sizeof(mx)); Finf=mx[];
chzero(,,n,,n);
for (i=;i<=n;++i)
{
for (tp=,k=p,x=sa[p][i],y=sa[p][i-];k>=;--k)
if (rak[k][x]==rak[k][y]) tp+=(<<k),x+=(<<k),y+=(<<k);
if (tp<n) chzero(,,n,tp+,n);
modify(,,n,,n,w[sa[p][i]]);
}
chzero(,,n,,n);
write(,,n);
}
Last Word
看到最后你知道小C一定又要吐槽了。(废话,小C发哪篇文章不是为了吐槽?)
做这题之前小C还从来没有写过后缀数组,上网找了一篇“公认”很优秀的后缀数组模板(这里不是指黄学长的)讲解,理解了半天,愣是没理解。
奇怪,这大概是小C唯一一个看着模板还依然没有思路的算法了吧。(明明是你智商比较低)
然后点开黄学长的博客,发现了画风迥异的后缀数组模板,小C当机立断就写这种风格的了。
虽然黄学长写得十分优雅,但感觉还是无法拯救自己的智商,全程抄代码,至今好像还有点蒙。(CTSC考试之前一直都在看后缀数组模板QAQ,还好没考)
这是其一。然后就是自己脑子一抽写了个线段树(败给了常数),结果学写了正解之后发现自己想不出原来的线段树做法了有木有?
写正解的时候也处处碰壁,直到遇到小D和小F两位大神的指(yi)点(tong)迷(nu)津(D),才理解可以把height理解为连接后缀数组的线段,思路瞬间清晰。
蒟 蒻 求 轻 D
[BZOJ]4199 品酒大会(Noi2015)的更多相关文章
-
BZOJ 4199 品酒大会
以前一直听说什么后缀数组height合并之类的 表示我这种后缀数组都敲不熟的蒟蒻怎么会写 但是做了做觉得还是很简单的嘛 这个题是有两问的,第一问是求LCP>=R的后缀对有多少个 这个就是AHOI ...
-
BZOJ 4199: [Noi2015]品酒大会 [后缀数组 带权并查集]
4199: [Noi2015]品酒大会 UOJ:http://uoj.ac/problem/131 一年一度的“幻影阁夏日品酒大会”隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品 ...
-
bzoj 4199: [Noi2015]品酒大会
Description 一年一度的"幻影阁夏日品酒大会"隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发"首席品酒家"和"首席猎手&quo ...
-
【刷题】BZOJ 4199 [Noi2015]品酒大会
Description 一年一度的"幻影阁夏日品酒大会"隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发"首席品酒家"和"首席猎手&quo ...
-
[LOJ 2133][UOJ 131][BZOJ 4199][NOI 2015]品酒大会
[LOJ 2133][UOJ 131][BZOJ 4199][NOI 2015]品酒大会 题意 给定一个长度为 \(n\) 的字符串 \(s\), 对于所有 \(r\in[1,n]\) 求出 \(s\ ...
-
bzoj 4199 &;&; NOI 2015 品酒大会
一年一度的“幻影阁夏日品酒大会”隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加. 在大会的晚餐上,调酒师 Rainbow 调制了 ...
-
4199. [NOI2015]品酒大会【后缀数组+并查集】
Description 一年一度的“幻影阁夏日品酒大会”隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品 酒家”和“首席猎手”两个奖项,吸引了众多品酒师参加.在大会的晚餐上,调酒师 ...
-
品酒大会 BZOJ 4199
品酒大会 [问题描述] [输入格式] [输出格式] [样例输入] 10ponoiiipoi 2 1 4 7 4 8 3 6 4 7 [样例输出] 45 56 10 56 3 32 0 0 0 0 0 ...
-
【BZOJ4199】【NOI2015】品酒大会(后缀数组)
[BZOJ4199][NOI2015]品酒大会 题面 BZOJ Uoj 洛谷 题解 考虑最裸的暴力 枚举每次的长度 以及两个开始的位置 检查以下是否满足条件,如果可以直接更新答案 复杂度\(O(n^3 ...
随机推荐
-
移动端图片上传解决方案localResizeIMG先压缩后ajax无刷新上传
现在科技太发达,移动设备像素越来越高,随便一张照片2M+,但是要做移动端图片上传和pc上略有不同,移动端你不能去限制图片大小,让用户先处理图片再上传,这样不现实.所以理解的解决方案就是在上传先进行图片 ...
-
maven常见问题
maven常见错误: 一..在Eclipse导入已存在工作空间的项目,出现 情况1: Scanning errors (1):1 Could not read pom.xml 就是pom.xml出现了 ...
-
顺序表及其多种实现方式 --- C/C++
所谓顺序表,即线性表的顺序存储结构.下面给出的是数据结构---线性表的定义. ADT List{ 数据对象: 线性表的数据对象的集合为{a1,a2,a3,...,an},每个元素的类型为ElemTyp ...
-
javac 命令用法
引用自己写的Class 在java中手动编译时,总提示找不到类,调试成功后,特把目录结构与编译成功的命令列出: 样例一: 文件名 MessageStore.java Hello.java 源码 pac ...
-
OD: Heap in Windows 2K &; XP SP1
Windows 堆溢出 MS 没有完全公开 Windows 的堆管理细节,目前对 Windows 堆的了解主要基于技术*者.黑客.安全专家.逆向工程师等的个人研究成果. 目前 Windows NT4 ...
-
linux中段错误的处理
在 Linux环境下做C语言项目,由于是在一个原有项目基础之上进行二次开发,而且项目工程庞大复杂,出现了不少问题,其中遇到最多.花费时间最长的问题就是著名的“段错误”(Segmentation Fau ...
-
开涛spring3(6.1) - AOP 之 6.1 AOP基础
6.1.1 AOP是什么 考虑这样一个问题:需要对系统中的某些业务做日志记录,比如支付系统中的支付业务需要记录支付相关日志,对于支付系统可能相当复杂,比如可能有自己的支付系统,也可能引入第三方支付平 ...
-
redis(二)
基本配置 在源文件/usr/local/redis目录下,文件redis.conf为配置文件 绑定地址:如果需要远程访问,可将此行注释 bind 127.0.0.1 端口,默认为6379 port 6 ...
-
PAT 1016 部分A+B
https://pintia.cn/problem-sets/994805260223102976/problems/994805306310115328 正整数A的“D~A~(为1位整数)部分”定义 ...
-
安装Logtail(Linux系统)
Logtail客户端是日志服务提供的日志采集客户端,请参考本文档,在Linux服务器上安装Logtail客户端. 支持的系统 支持如下版本的Linux x86-64(64位)服务器: Aliyun L ...