loj 6198

题目链接

题意

给了一个字符串,字符串每个位置有一个权值 w i wi wi.定义字符串的两个后缀 i , j i,j i,j的价值为 L C P ( i , j ) + LCP(i,j)+ LCP(i,j)+ w i wi wi xor w j wj wj

求最大价值

解法

首先分开考虑:对于 L C P ( i , j ) LCP(i,j) LCP(i,j),可以先求出sa,和height,然后是[i,j]的区间min.
对于 w i wi wi xor w j wj wj .,可以用01trie求.
然后把两个价值统一起来的方法就是把height从大到小排序,保证每个加进去的都是最小的一个,然后还需要用并查集维护现在一个区间有哪些位置,然后就用trie在这些数里选出最大值就可以了.并查集合并的时候需要启发式合并.

注意:我现在对sa的记忆和理解有写模糊了.需要在这里备忘一下:
sa[]:排名为i的后缀是哪个
rk[]:第i个后缀的排名
h[]:排名为i的后缀和排名为i-1的后缀的lcp

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
const int mx=20;
inline int read(){
	char c=getchar();int t=0,f=1;
	while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
	while(isdigit(c)){t=(t<<3)+(t<<1)+(c^48);c=getchar();}
	return t*f;
}
int n,w[maxn],x[maxn],y[maxn],sa[maxn],rk[maxn],h[maxn],t[maxn],m=300,c[maxn*mx][2];
char s[maxn];
void get(){
	for(int i=1;i<=n;i++)t[x[i]=s[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 num=0;
		for(int i=n-k+1;i<=n;i++)y[++num]=i;
		for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
		memset(t,0,sizeof(t));
		for(int i=1;i<=n;i++)t[x[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];y[i]=0;}
		swap(x,y);
		x[sa[1]]=1;num=1;
		for(int i=2;i<=n;i++)
		x[sa[i]]=(y[sa[i]]==y[sa[i-1]])&&(y[sa[i]+k]==y[sa[i-1]+k])?num:++num;
		if(num==n)break;
		m=num;
	}
	for(int i=1;i<=n;i++)rk[sa[i]]=i;
	int k=0;
	for(int i=1;i<=n;i++){//求height,枚举i而不是rk[i]
		if(k)k--;int j=sa[rk[i]-1];
		while(s[i+k]==s[j+k]&&i+k<=n&&j+k<=n)k++;
		h[rk[i]]=k;
	}
}
int sz[maxn],f[maxn],rt[maxn],cnt;
inline int find(int x){
	return f[x]==x?f[x]:f[x]=find(f[x]);
}
int build(int x){
	int rt=++cnt,now=rt;
	for(int i=mx;i>=0;i--){
		c[now][(x>>i)&1]=++cnt,now=cnt;
	}
	return rt;
}
typedef pair<int,int> pii;
pii so[maxn];
int query(int x,int y,int z=0){
	int best=z;
	if(c[x][0]){
		if(c[y][1])best=query(c[x][0],c[y][1],2*z+1);
		else best=query(c[x][0],c[y][0],2*z);
	}
	if(c[x][1]){
		if(c[y][0])best=max(best,query(c[x][1],c[y][0],2*z+1));
		else best=max(best,query(c[x][1],c[y][1],2*z));
	}
	return best;
}
int ans;
void merge(int x,int y){//trie树合并类似线段树合并,这里不需要新建节点是因为没有修改.
	if(c[x][0]){
		if(c[y][0])merge(c[x][0],c[y][0]);
		else c[y][0]=c[x][0];
	}
	if(c[x][1]){
		if(c[y][1])merge(c[x][1],c[y][1]);
		else c[y][1]=c[x][1];
	}
}
int main(){
	n=read();
	scanf("%s",s+1);
	for(int i=1;i<=n;i++)w[i]=read();
	get();
	for(int i=1;i<=n;i++){
		sz[i]=1,f[i]=i;rt[i]=build(w[sa[i]]);
	}
	for(int i=2;i<=n;i++){
		so[i-1]=pii(n-h[i],i);
	}
	sort(so+1,so+n);
	int ret=0;
	for(int i=1;i<n;i++){
		int x=so[i].second,y=x-1,z=n-so[i].first;
		x=find(x),y=find(y);
		if(sz[x]<sz[y])swap(x,y);
		int tmp=query(rt[y],rt[x])+z;
		if(tmp>ans)ans=tmp;
		sz[x]+=sz[y];
		merge(rt[y],rt[x]);
		f[y]=x;
	}
	printf("%d\n",ans);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值