【BZOJ4199】【NOI2015】品酒大会

题面

BZOJ

Uoj

洛谷

题解

考虑最裸的暴力

枚举每次的长度

以及两个开始的位置

检查以下是否满足条件,如果可以直接更新答案

复杂度\(O(n^3)\)

\(15~20\)分

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 320000
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
int n;
char s[MAX];
int lg[MAX],v[MAX];
struct SA
{
int p[20][MAX],a[MAX];
int x[MAX],y[MAX],t[MAX];
int SA[MAX],height[MAX],rk[MAX];
bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
void init()
{
memset(SA,0,sizeof(SA));
memset(height,0,sizeof(height));
memset(rk,0,sizeof(rk));
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
memset(t,0,sizeof(t));
memset(a,0,sizeof(a));
}
void GetSA()
{
int m=50;
for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int p=0;
for(int i=n-k+1;i<=n;++i)y[++p]=i;
for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
for(int i=0;i<=m;++i)t[i]=0;
for(int i=1;i<=n;++i)t[x[y[i]]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
swap(x,y);
x[SA[1]]=p=1;
for(int i=2;i<=n;++i)
x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
if(p>=n)break;
m=p;
}
for(int i=1;i<=n;++i)rk[SA[i]]=i;
for(int i=1,j=0;i<=n;++i)
{
if(j)--j;
while(a[i+j]==a[SA[rk[i]-1]+j])++j;
height[rk[i]]=j;
}
}
void Pre()
{
memset(p,63,sizeof(p));
for(int i=1;i<=n;++i)p[0][i]=height[i];
for(int j=1;j<20;++j)
for(int i=1;i<=n;++i)
p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
}
int Query(int i,int j)
{
return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
}
int lcp(int i,int j)
{
int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
return Query(l,r);
}
}SA;
int main()
{
n=read();
for(int i=2;i<=n;++i)lg[i]=lg[i>>1]+1;
scanf("%s",s+1);
for(int i=1;i<=n;++i)SA.a[i]=s[i]-96;
SA.GetSA();SA.Pre();
for(int i=1;i<=n;++i)v[i]=read();
for(int len=0;len<n;++len)
{
ll ans1=0,ans2=-1e18;
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
{
if(SA.lcp(i,j)>=len)
ans1++,ans2=max(ans2,1ll*v[i]*v[j]);
}
if(ans1)printf("%lld %lld\n",ans1,ans2);
else
{
for(int j=len;j<n;++j)puts("0 0");
break;
}
}
return 0;
}

继续考虑,

观察到如果两杯酒是\(k\)相似的

那么,他们一定是\(j(j<=k)\)相似的

随意只需要枚举两杯酒

检查他们是多少相似

然后做一个前缀和就好了

复杂度\(O(n^2)\)

\(40\)分

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 320000
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
int n;
char s[MAX];
int lg[MAX],v[MAX];
ll ans1[MAX],ans2[MAX];
struct SA
{
int p[20][MAX],a[MAX];
int x[MAX],y[MAX],t[MAX];
int SA[MAX],height[MAX],rk[MAX];
bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
void init()
{
memset(SA,0,sizeof(SA));
memset(height,0,sizeof(height));
memset(rk,0,sizeof(rk));
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
memset(t,0,sizeof(t));
memset(a,0,sizeof(a));
}
void GetSA()
{
int m=50;
for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int p=0;
for(int i=n-k+1;i<=n;++i)y[++p]=i;
for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
for(int i=0;i<=m;++i)t[i]=0;
for(int i=1;i<=n;++i)t[x[y[i]]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
swap(x,y);
x[SA[1]]=p=1;
for(int i=2;i<=n;++i)
x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
if(p>=n)break;
m=p;
}
for(int i=1;i<=n;++i)rk[SA[i]]=i;
for(int i=1,j=0;i<=n;++i)
{
if(j)--j;
while(a[i+j]==a[SA[rk[i]-1]+j])++j;
height[rk[i]]=j;
}
}
void Pre()
{
memset(p,63,sizeof(p));
for(int i=1;i<=n;++i)p[0][i]=height[i];
for(int j=1;j<20;++j)
for(int i=1;i<=n;++i)
p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
}
int Query(int i,int j)
{
return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
}
int lcp(int i,int j)
{
int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
return Query(l,r);
}
}SA;
int main()
{
n=read();
for(int i=2;i<=n;++i)lg[i]=lg[i>>1]+1;
scanf("%s",s+1);
for(int i=1;i<=n;++i)SA.a[i]=s[i]-96;
SA.GetSA();SA.Pre();
for(int i=1;i<=n;++i)v[i]=read();
memset(ans2,-63,sizeof(ans2));
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
{
int len=SA.lcp(i,j);
ans1[0]++;ans1[len+1]--;
ans2[len]=max(ans2[len],1ll*v[i]*v[j]);
}
for(int i=1;i<=n;++i)ans1[i]+=ans1[i-1];
for(int i=n;i>=0;--i)ans2[i]=max(ans2[i],ans2[i+1]);
for(int i=0;i<n;++i)printf("%lld %lld\n",ans1[i],!ans1[i]?0:ans2[i]);
return 0;
}

如果我们求出\(height\)数组之后

枚举一个长度\(len\),按照\(height\)分类

如果一段连续的\(height\)都不小于了\(len\)

证明这一段都会产生贡献

所以记录这一段产生的贡献,

至于最大值,就在这一段里面记录最大,次大,最小,次小值

拼起来算一下

因为\(height\)最大值可以很小

所以这样可以过\(50\)分

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 320000
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
int n;
char s[MAX];
int lg[MAX],v[MAX];
ll ans1[MAX],ans2[MAX];
struct SA
{
int p[20][MAX],a[MAX];
int x[MAX],y[MAX],t[MAX];
int SA[MAX],height[MAX],rk[MAX];
bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
void init()
{
memset(SA,0,sizeof(SA));
memset(height,0,sizeof(height));
memset(rk,0,sizeof(rk));
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
memset(t,0,sizeof(t));
memset(a,0,sizeof(a));
}
void GetSA()
{
int m=50;
for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int p=0;
for(int i=n-k+1;i<=n;++i)y[++p]=i;
for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
for(int i=0;i<=m;++i)t[i]=0;
for(int i=1;i<=n;++i)t[x[y[i]]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
swap(x,y);
x[SA[1]]=p=1;
for(int i=2;i<=n;++i)
x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
if(p>=n)break;
m=p;
}
for(int i=1;i<=n;++i)rk[SA[i]]=i;
for(int i=1,j=0;i<=n;++i)
{
if(j)--j;
while(a[i+j]==a[SA[rk[i]-1]+j])++j;
height[rk[i]]=j;
}
}
void Pre()
{
memset(p,63,sizeof(p));
for(int i=1;i<=n;++i)p[0][i]=height[i];
for(int j=1;j<20;++j)
for(int i=1;i<=n;++i)
p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
}
int Query(int i,int j)
{
return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
}
int lcp(int i,int j)
{
int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
return Query(l,r);
}
}SA;
bool cmp(int a,int b){return SA.height[a]>SA.height[b];}
int id[MAX];
void update(int x,int &zd,int &cd,int &zx,int &cx)
{
if(x>zd)cd=zd,zd=x;
else if(x>cd)cd=x;
if(x<zx)cx=zx,zx=x;
else if(x<cx)cx=x;
}
ll check_max(int zd,int cd,int zx,int cx)
{
ll ret=-1e18;
if(zd!=-2e9&&cd!=-2e9)ret=max(ret,1ll*zd*cd);
if(zx!=+2e9&&cx!=+2e9)ret=max(ret,1ll*zx*cx);
return ret;
}
int main()
{
n=read();
for(int i=2;i<=n;++i)lg[i]=lg[i>>1]+1;
scanf("%s",s+1);
for(int i=1;i<=n;++i)SA.a[i]=s[i]-96;
SA.GetSA();SA.Pre();
for(int i=1;i<=n;++i)v[i]=read(),id[i]=i;;
memset(ans2,-63,sizeof(ans2)); sort(&id[1],&id[n+1],cmp); for(int len=0;len<=n;++len)
{
int zd,zx,cd,cx,cnt=0;
zd=cd=-2e9;zx=cx=2e9;
for(int i=2;i<=n;++i)
{
if(SA.height[i]<len)
{
ans2[len]=max(ans2[len],check_max(zd,cd,zx,cx));
zd=cd=-2e9;zx=cx=2e9;
cnt=0;
}
else
{
update(v[SA.SA[i]],zd,cd,zx,cx);
if(!cnt)update(v[SA.SA[i-1]],zd,cd,zx,cx);
ans1[len]+=cnt;
if(!cnt)ans1[len]++,cnt++;
cnt++;
}
}
ans2[len]=max(ans2[len],check_max(zd,cd,zx,cx));
if(!ans1[len])break;
}
for(int i=n;i>=0;--i)ans2[i]=max(ans2[i],ans2[i+1]);
for(int i=0;i<n;++i)printf("%lld %lld\n",ans1[i],!ans1[i]?0:ans2[i]);
return 0;
}

想想上面的东西怎么优化?

我们每次从小往大枚举

如果有一段连续的\(height\)都大于了\(len\)

那么,我们在\(0..len-1\)的时候也都会被枚举一遍

所以,我们考虑从大到小枚举

如果有一段连续的区间,那我们可以直接把他们缩成一个区间

同时记录这个区间的大小,以及最大,最小值

这样的话,每次的枚举可以把一段区间变成一个点

考虑这个思路,也不可能每次扫一边所有的值

所以直接把\(height\)从大到小排序

每次处理一个\(height\)就合并两个集合

并且计算产生的贡献

最后求一个后缀和就好

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 320000
inline int read()
{
int x=0,t=1;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
int n,v[MAX];
char s[MAX];
ll ans1[MAX],ans2[MAX];
struct SA
{
int a[MAX];
int x[MAX],y[MAX],t[MAX];
int SA[MAX],height[MAX],rk[MAX];
bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
void GetSA()
{
int m=50;
for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int p=0;
for(int i=n-k+1;i<=n;++i)y[++p]=i;
for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
for(int i=0;i<=m;++i)t[i]=0;
for(int i=1;i<=n;++i)t[x[y[i]]]++;
for(int i=1;i<=m;++i)t[i]+=t[i-1];
for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
swap(x,y);
x[SA[1]]=p=1;
for(int i=2;i<=n;++i)
x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
if(p>=n)break;
m=p;
}
for(int i=1;i<=n;++i)rk[SA[i]]=i;
for(int i=1,j=0;i<=n;++i)
{
if(j)--j;
while(a[i+j]==a[SA[rk[i]-1]+j])++j;
height[rk[i]]=j;
}
}
}SA;
bool cmp(int a,int b){return SA.height[a]>SA.height[b];}
int id[MAX];
int f[MAX],mm[MAX],mi[MAX],size[MAX];
ll ans[MAX];
int getf(int x){return x==f[x]?x:f[x]=getf(f[x]);}
void Merge(int x,int y,int len)
{
x=getf(x);y=getf(y);
f[y]=x;
ans1[len]+=1ll*size[x]*size[y];
size[x]+=size[y];
ans[x]=max(ans[x],ans[y]);
ans[x]=max(ans[x],max(1ll*mm[x]*mm[y],1ll*mi[x]*mi[y]));
ans[x]=max(ans[x],max(1ll*mm[x]*mi[y],1ll*mi[x]*mm[y]));
mm[x]=max(mm[x],mm[y]);
mi[x]=min(mi[x],mi[y]);
ans2[len]=max(ans2[len],ans[x]);
}
int main()
{
n=read();
scanf("%s",s+1);
for(int i=1;i<=n;++i)SA.a[i]=s[i]-96;
SA.GetSA();
for(int i=1;i<=n;++i)v[i]=read(),id[i]=i;;
for(int i=1;i<=n;++i)f[i]=i,size[i]=1,mm[i]=mi[i]=v[i],ans[i]=-1e18;
memset(ans2,-63,sizeof(ans2));
sort(&id[2],&id[n+1],cmp);
for(int i=2;i<=n;++i)
Merge(SA.SA[id[i]],SA.SA[id[i]-1],SA.height[id[i]]);
for(int i=n;i>=0;--i)ans1[i]+=ans1[i+1];
for(int i=n;i>=0;--i)ans2[i]=max(ans2[i],ans2[i+1]);
for(int i=0;i<n;++i)printf("%lld %lld\n",ans1[i],!ans1[i]?0:ans2[i]);
return 0;
}

【BZOJ4199】【NOI2015】品酒大会(后缀数组)的更多相关文章

  1. [UOJ#131][BZOJ4199][NOI2015]品酒大会 后缀数组 + 并查集

    [UOJ#131][BZOJ4199][NOI2015]品酒大会 试题描述 一年一度的“幻影阁夏日品酒大会”隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品酒家”和“首席猎手”两个 ...

  2. 【BZOJ4199】[Noi2015]品酒大会 后缀数组+并查集

    [BZOJ4199][Noi2015]品酒大会 题面:http://www.lydsy.com/JudgeOnline/wttl/thread.php?tid=2144 题解:听说能用SAM?SA默默 ...

  3. BZOJ 4199: [Noi2015]品酒大会 [后缀数组 带权并查集]

    4199: [Noi2015]品酒大会 UOJ:http://uoj.ac/problem/131 一年一度的“幻影阁夏日品酒大会”隆重开幕了.大会包含品尝和趣味挑战两个环节,分别向优胜者颁发“首席品 ...

  4. 【BZOJ-4199】品酒大会 后缀数组 + 并查集合并集合

    4199: [Noi2015]品酒大会 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 436  Solved: 243[Submit][Status] ...

  5. [NOI2015] 品酒大会 - 后缀数组,并查集,STL,启发式合并

    [NOI2015] 品酒大会 Description 对于每一个 \(i \in [0,n)\) 求有多少对后缀满足 LCP 长度 \(\le i\) ,并求满足条件的两个后缀权值乘积的最大值. So ...

  6. BZOJ 4199: [Noi2015]品酒大会( 后缀数组 + 并查集 )

    求出后缀数组后, 对height排序, 从大到小来处理(r相似必定是0~r-1相似), 并查集维护. 复杂度O(NlogN + Nalpha(N)) ------------------------- ...

  7. BZOJ.4199.[NOI2015]品酒大会(后缀数组 单调栈)

    BZOJ 洛谷 后缀自动机做法. 洛谷上SAM比SA慢...BZOJ SAM却能快近一倍... 显然只需要考虑极长的相同子串的贡献,然后求后缀和/后缀\(\max\)就可以了. 对于相同子串,我们能想 ...

  8. NOI2015品酒大会 后缀数组

    自己尝试敲后缀数组,发现难看(tiao)的不行,于是抄了板子 考虑建出hei以后转化出的问题: 对于一个数组中权值大于等于k的连续部分,求取两个数的方案数和两数积的最大值 (好气啊,可以有负数) 把询 ...

  9. 【学术篇】NOI2015 品酒大会 后缀数组+并查集

    省选前大致是刷不了几道题了... 所以就找一些裸一点的题目练练板子算了= = 然而这题一点都不裸, 也并不怎么好写... 于是就浪费了将近一下午的时间... 然而还不是因为后缀数组板子不熟= = 首先 ...

  10. luoguP2178 [NOI2015]品酒大会(后缀数组做法)

    题意 因为一个\(k\)相似必定为\(k-1,k-2....0\)相似,对于一个\(lcp\)为\(k\)后缀对\((i,j)\),我们只用把它的贡献加在\(k\)的答案上,最后求一个后缀和和后缀ma ...

随机推荐

  1. Angular20 nginx安装,angular项目部署

    1 nginx安装(Windows版本) 1.1 下载安装包 到官网下载Windows版本的nginx安装包 技巧01:下载好的压缩包解压即可,无需安装 1.2 启动nginx 进入到解压目录,点击 ...

  2. phpstorm使用之——常用快捷键

    phpstorm使用之--常用快捷键 使用IDE的根本所在乃是为了提高工作效率. windows下phpstorm的快捷键 ctrl+shift+n查找文件 ctrl+shift+f 在一个目录里查找 ...

  3. IOS开发之记录用户登陆状态,ios开发用户登陆

    IOS开发之记录用户登陆状态,ios开发用户登陆 上一篇博客中提到了用CoreData来进行数据的持久化,CoreData的配置和使用步骤还是挺复杂的.但熟悉CoreData的使用流程后,CoreDa ...

  4. FreeSWITCH 内线拨号 总是使用 dialplan/public 拨号计划,而对 dialplan/default 视而不见

    FreeSWITCH 内线拨号 总是使用 dialplan/public 拨号计划,而对 dialplan/default 视而不见 昨天还是 好好的额,  今天 就这样了, 导致 配置都乱了, 搞了 ...

  5. 利用ajax获取网页表单数据,并存储到数据库之一(使用JDBC)

    所谓JDBC就是利用java与数据库相连接的技术,从数据库获取既有的信息或者把网页上的信息存储到数据库. 这里简单的介绍公司的一个小项目中的一部分,由于代码较多,所以用图片形式进行展示.源码请查看源码 ...

  6. ireport报表学习

    常用组件介绍: 制作一个报表一般四个组件比较常用,下面分别介绍 Rectangle:用于画表格的样式,整个表格的样式使用次组件做出来的,本控件表现为一个黑色矩形框,多个黑色矩形框排在一起可以组合出来任 ...

  7. Eclipse (eclipse-jee-luna-SR2-win32)4.4.2 , jdk1.7, pydev 4.5.5版本的 完成的python环境集成

    说明: 下面的搭建python2.x环境需要的条件: jdk1.7 eclipse(版本小于4.5) pydev(版本小于5.0) Eclipse和PyDev搭建完美Python开发环境 Window ...

  8. Centos下_MysqL5.7在使用mysqldump命令备份数据库报错:mysqldump: Got error: 1449: The user specified as a definer ('fk_system'@'localhost') does not exist when using LOCK TABLES

    在阿里云服务器增加一个shell脚本定时备份数据库脚本执行任务时,测试性的执行了备份命令,如下 [root@iZ2ze503xw2q1fftv5rhboZ mysql_bak]# /usr/local ...

  9. CodeForces - 796D Police Stations bfs

    思路:删除尽量多的边使得所有点都能在限制距离之内到达一个警局,删除边会形成多棵子树,最多只能k棵.其实就是以每个警局为根结点,把整棵树划分为以警局为根结点的k棵树,说明要删除的边的数量就是k-1条,即 ...

  10. Dijkstra and Floyd算法

    Dijkstra算法 算法思想:设G=(V,E)是一个带权有向图,把图中顶点集合V分成两组,第一组为已求出最短路径的顶点集合(用S表示,初始时S中只有一个源点,以后每求得一条最短路径 , 就将加入到集 ...