【NOI2015】品酒大会

后缀自动机

首先考虑第一问:

    我们先将后缀自动机建出来,考虑每个节点,它出现的次数肯定是endpos的size(我们记为num),那么选取这个节点的串的方案数即为C(num,2)=num*(num-1)/2,所能贡献的长度区间为这个节点对应的所有串的长度即[ len[fa[x]]+1 , len[x]+1 ],这里可以差分一下,最后前缀和就是答案了。

然后考虑第二问:

    我们考虑每当我们在后缀自动机上新建一个节点,就把这个位置的a[i]给赋值上去。当然,在建的过程中由于endpos裂开而产生的的那个新的节点不赋值,因为最后因endpos裂开的而产生的新节点的nq在parent tree上一定是原节点q的father,所以最后的值会更新上去,如果赋值了,就相当于额外多了一个值,答案当然会不对。

    那么我们再考虑在parent tree上应该怎么做。在parent tree上,father一定是son的后缀,而对于这题来说,某两个串是r相似的,那么它们一定是[0,r-1]相似的,即一对r相似串的a1*a2有贡献那么对它们的前缀一定也有贡献,所以我们可以将串倒序建后缀自动机,这样后缀就变成了前缀,lcp就变成了lca!因为此题a有负数,所以我们要维护最大次大最小次小,那么因为现在parent tree上每个父亲都是儿子的前缀,所以子树的最优可以更新父亲的最优。那么自然的我们就可以对每个节点维护四个值(最大次大最小次小),然后每个节点的值为它即它所有节点的最后(树形dp转移),然后贡献的区间显然为[ len[fa[x]]+1 , len[x]+1 ],这个我们可以用线段树维护一下,这是直接可以标记永久化的,也非常好写。

效率:O(nlogn)

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <vector>
#define pb push_back
#define lch a[n].lc
#define rch a[n].rc
using namespace std;
const long long inf=1e18+10;
const int maxn=6e5+10;
int n;
char s[maxn];
long long vvv[maxn];
struct suda
{
	struct da {int ch[30],fa,len;}po[maxn];
	vector<long long>now[maxn];	
	struct daa{int lc,rc;long long tag;}a[4*maxn];
	int las,tot,si[maxn],to[maxn],nt[maxn],st[maxn],topt,root,cnt;
	long long v[maxn],sum[maxn],ansv[maxn],ma[maxn],cima[maxn],mi[maxn],cimi[maxn];
	void init()
	{
		memset(po,0,sizeof po); memset(si,0,sizeof si);
		memset(to,0,sizeof to); memset(nt,0,sizeof nt);
		memset(st,0,sizeof st); memset(v,0,sizeof v);
		memset(sum,0,sizeof sum);
		las=tot=1; topt=0; //po[0].len=-1;
		for (int i=1;i<=6e5;i++) 
		{
			now[i].clear(); ansv[i]=-inf;
			ma[i]=cima[i]=-inf;
			mi[i]=cimi[i]=inf;
		}
	}
	void insert(int x,long long val)
	{
		int p=las,np=las=++tot; si[np]=1; po[np].len=po[p].len+1;
		ma[np]=val; mi[np]=val; //printf("%d addv-> %lld\n",np,val);
		for (;p&&(!po[p].ch[x]);p=po[p].fa) po[p].ch[x]=np;
		if (!p) po[np].fa=1;
		else
		{
			int q=po[p].ch[x];
			if (po[q].len==po[p].len+1) po[np].fa=q;
			else
			{
				int nq=++tot; po[nq]=po[q];
				po[nq].len=po[p].len+1;
				po[q].fa=po[np].fa=nq;
				for (;p&&(po[p].ch[x]==q);p=po[p].fa) po[p].ch[x]=nq;
			}
		}
	}
	void add(int x,int y){to[++topt]=y; nt[topt]=st[x]; st[x]=topt;}
	void build_tree(int &n,int l,int r)
	{
		n=++cnt; a[n].tag=-inf;
		if (l==r) return ;
		int mid=(l+r)>>1;
		build_tree(lch,l,mid); build_tree(rch,mid+1,r);
	}
	void tree_max(int n,int L,int R,int l,int r,long long kk)
	{
		if (L==l && R==r) 
		{
			a[n].tag=max(a[n].tag,kk);
			//if (l==r) printf("kkqqqqq %lld\n",a[n].tag);
			return;
		}
		int mid=(L+R)>>1;
		if (r<=mid) tree_max(lch,L,mid,l,r,kk);
		else if (l>=mid+1) tree_max(rch,mid+1,R,l,r,kk);
		else tree_max(lch,L,mid,l,mid,kk),tree_max(rch,mid+1,R,mid+1,r,kk);
	}
	void qury(int n,int l,int r,int lc)
	{
		ansv[lc]=max(ansv[lc],a[n].tag);
		if (l==r) return;
		int mid=(l+r)>>1;
		if (lc<=mid) qury(lch,l,mid,lc);else qury(rch,mid+1,r,lc);
	}
	void getsi(int x)
	{
		int p=st[x];
		while (p)
		{
			getsi(to[p]); si[x]+=si[to[p]];

			if (ma[to[p]]>=ma[x]) {cima[x]=ma[x]; ma[x]=ma[to[p]];}
			else if (ma[to[p]]>cima[x]) cima[x]=ma[to[p]];

			if (cima[to[p]]>=ma[x]) {cima[x]=ma[x]; ma[x]=cima[to[p]];}
			else if (cima[to[p]]>cima[x]) cima[x]=cima[to[p]];

			if (mi[to[p]]<=mi[x]) {cimi[x]=mi[x]; mi[x]=mi[to[p]];}
			else if (mi[to[p]]<cimi[x]) cimi[x]=mi[to[p]];

			if (cimi[to[p]]<=mi[x]) {cimi[x]=mi[x]; mi[x]=cimi[to[p]];}
			else if (cimi[to[p]]<cimi[x]) cimi[x]=cimi[to[p]];

			p=nt[p];
		}
		if (x==1) v[0]+=1ll*si[x]*(si[x]-1)/2ll;
		else
		{
			v[po[po[x].fa].len+1]+=1ll*si[x]*(si[x]-1)/2ll;
			v[po[x].len+1]-=1ll*si[x]*(si[x]-1)/2ll;
		}
		if (si[x]>=2 && x!=1) 
		{
			//printf("kkqq %d %d %lld %lld\n",po[po[x].fa].len+1,po[x].len,ma[x]*cima[x],mi[x]*cimi[x]);
			if (ma[x]!=-inf && cima[x]!=-inf)
			tree_max(root,1,n,po[po[x].fa].len+1,po[x].len,ma[x]*cima[x]);
			if (mi[x]!=inf && cimi[x]!=inf)
			tree_max(root,1,n,po[po[x].fa].len+1,po[x].len,mi[x]*cimi[x]);
		}
		//printf("kkqq %d %d\n",x,si[x]);
		//if (si[x]) now[si[x]].pb(po[x].vv),printf("kkqqkkqq %d si[x]=%d %lld\n",x,si[x],po[x].vv);
	}
	void solve()
	{
		for (int i=1;i<=tot;i++) 
		{
			add(po[i].fa,i);
			//printf("kqkq %d %d\n",po[i].fa,i);
			//printf("kqkqk %d len -> (%d,%d)\n",i,po[po[i].fa].len+1,po[i].len);
		}
		build_tree(root,1,n); getsi(1);
		//qury(root,1,n,1); printf("cknow %lld\n",ansv[1]); 
		for (int i=1;i<=n;i++) sum[i]=sum[i-1]+v[i];
		sum[0]=v[0];
		//for (int i=1;i<=tot;i++) 
		//printf("kkkqqq (%d,%d) 1=%lld 2=%lld 3=%lld 4=%lld\n",po[po[i].fa].len+1,po[i].len,ma[i],cima[i],mi[i],cimi[i]);
		for (int i=0;i<n;i++) 
		{
			if (i) qury(root,1,n,i);
			else ansv[i]=max(vvv[n]*vvv[n-1],vvv[1]*vvv[2]);
			//if (i==1) printf("kqkq %lld\n",ansv[i]);
			if (!sum[i]) ansv[i]=0;
			printf("%lld %lld\n",sum[i],ansv[i]);
		}
	}
}SAM;
int main()
{
	scanf("%d%s",&n,s+1); SAM.init();
	for (int i=1;i<=n;i++) scanf("%lld",&vvv[i]);
	for (int i=1;i<=n;i++) SAM.insert(s[n-i+1]-'a',vvv[n-i+1]);
	sort(vvv+1,vvv+n+1);
	SAM.solve();
return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值