[NOI2015]品酒大会 【SAM】

传送门


SOL

给定一个母串 S S S
题意:对于长度为 l e n len len的子串集合 a i a_{i} ai,
a i a_{i} ai中相同字串(不同位置)之间的组合方案之和,和 a i a_{i} ai 中 任意两字串权值乘积的最大值
一子串权值为该子串最左端字符对应的权值(可能为负数)。


求出每一子串出现总数 c n t cnt cnt, d [ i ] d[i] d[i]表示长度为 i i i的情况之和,
d [ i ] + = c n t ∗ ( c n t − 1 ) / 2 d[i]+=cnt*(cnt-1)/2 d[i]+=cnt(cnt1)/2;

反向建出SAM,同一节点里的权值相同,维护最大,次大,最小,次小,上传到父亲,
用线段树区间更新 l e n [ f a [ i ] ] + 1 len[fa[i]]+1 len[fa[i]]+1—— l e n [ i ] len[i] len[i]区间的答案

Update 2019.08.07:
好像不用线段树用差分就可以。。。留给读者自己思考吧(这个不难想)


CODE

#include<bits/stdc++.h>
#define pf printf
#define sf scanf
#define re register
using namespace std;
#define cs const
#define ll long long
#define lc (p<<1)
#define rc ((p<<1)|1)
cs int N=6e5+10;
cs ll inf=1e18;
ll dlt[N],cnt[N];
int vl[N],len[N],fa[N],ch[N][30],tot,las,n,d[N],g[N];
ll num[N<<1],mx[N],dmx[N],mn[N],dmn[N];
inline void  build(int p,int l,int r){
	num[p]=-inf;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
}
inline void update(int p,int l,int r,int ql,int qr,ll v){
	if(ql>qr)return;
	if(ql<=l&&r<=qr){
		num[p]=max(num[p],v);
		return;
	}
	int mid=(l+r)>>1;;
	if(qr<=mid)update(lc,l,mid,ql,qr,v);
	else if(ql>mid)update(rc,mid+1,r,ql,qr,v);
	else update(lc,l,mid,ql,qr,v),update(rc,mid+1,r,ql,qr,v);
}
struct node{
	ll mx;
	int id,l,r;
};
inline void bfs(){
	queue<node> q;
	q.push((node){num[1],1,1,n});
	while(!q.empty()){
		node t=q.front();q.pop();
		if(t.l==t.r){
			mx[t.l]=t.mx;
			continue;
		}
		int mid=(t.l+t.r)>>1;
		q.push((node){max(t.mx,num[t.id<<1]),t.id<<1,t.l,mid});
		q.push((node){max(t.mx,num[(t.id<<1)|1]),(t.id<<1)|1,mid+1,t.r});
	}
}
inline void init(){
	las=tot=1;
}
inline void add(int c){
	int cur=++tot,p=las,q=ch[p][c];
	las=cur;len[cur]=len[p]+1;mx[cur]=mn[cur]=vl[len[cur]];cnt[cur]=1;
	for(;p&&!q;p=fa[p],q=ch[p][c])ch[p][c]=cur;
	if(!p){fa[cur]=1;return;}
	if(len[p]+1==len[q]){fa[cur]=q;return;}
	int clo=++tot;
	fa[clo]=fa[q];fa[q]=fa[cur]=clo;
	len[clo]=len[p]+1;
	memcpy(ch[clo],ch[q],sizeof ch[clo]);
	for(;p&&ch[p][c]==q;p=fa[p])ch[p][c]=clo;	
}
inline void get(){
	for(int re i=1;i<=tot;++i)++d[len[i]];
	for(int re i=1;i<=tot;++i)d[i]+=d[i-1];
	for(int re i=1;i<=tot;++i)g[d[len[i]]--]=i;
	for(int re i=tot;i>=2;--i){
		cnt[fa[g[i]]]+=cnt[g[i]];
		ll now=(cnt[g[i]]-1)*cnt[g[i]]/2;
		dlt[len[fa[g[i]]]+1]+=now;
		dlt[len[g[i]]+1]-=now;	
		if(mx[fa[g[i]]]>mx[g[i]]){
			if(dmx[fa[g[i]]]<mx[g[i]])dmx[fa[g[i]]]=mx[g[i]];
		}
		else {
			dmx[fa[g[i]]]=mx[fa[g[i]]];mx[fa[g[i]]]=mx[g[i]];
			if(dmx[g[i]]>dmx[fa[g[i]]])dmx[fa[g[i]]]=dmx[g[i]];
		}
		if(mn[fa[g[i]]]<mn[g[i]]){
			if(dmn[fa[g[i]]]>mn[g[i]])dmn[fa[g[i]]]=mn[g[i]];
		}
		else {
			dmn[fa[g[i]]]=mn[fa[g[i]]];mn[fa[g[i]]]=mn[g[i]];
			if(dmn[g[i]]<dmn[fa[g[i]]])dmn[fa[g[i]]]=dmn[g[i]];
		}
		if(dmx[g[i]]!=-inf)update(1,1,n,len[fa[g[i]]]+1,len[g[i]],max(mx[g[i]]*dmx[g[i]],mn[g[i]]*dmn[g[i]]));
		
	}
	bfs();
	for(int re i=1;i<=n-1;++i){
		dlt[i]+=dlt[i-1];
		printf("%lld %lld\n",dlt[i],dlt[i]==0? 0:mx[i]);
	}
}
char s[N];
ll lmx=-inf,ldmx=-inf,lmn=inf,ldmn=inf;
signed main (){
	init();
	sf("%d",&n);
	sf("%s",s+1);
	for(int re i=1;i<=n*2;++i)mx[i]=dmx[i]=-inf,mn[i]=dmn[i]=inf;
	build(1,1,n);
	for(int re i=n;i>=1;--i){
		sf("%d",&vl[i]);
		if(vl[i]>lmx)ldmx=lmx,lmx=vl[i];
		else if(vl[i]>ldmx)ldmx=vl[i];
		if(vl[i]<lmn)ldmn=lmn,lmn=vl[i];
		else if(vl[i]<ldmn)ldmn=vl[i];	
	}
	for(int re i=n;i>=1;--i)add(s[i]-'a');
	printf("%lld %lld\n",1ll*n*(n-1)/2,max(lmx*ldmx,ldmn*lmn));
	get();
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值