【BZOJ】4199: [Noi2015]品酒大会-SAM/SA

传送门:bzoj4199


题解

法1(SA):

l c p ( s u f ( i ) , s u f ( j ) ) = m i n ( h e i g h t [ r k [ i ] + 1 ] , h e i g h t [ r k [ i ] + 2 ] , . . , h e i g h t [ r k [ j ] ] ) , ( r k [ i ] < r k [ j ] ) lcp(suf(i),suf(j))=min(height[rk[i]+1],height[rk[i]+2],..,height[rk[j]]),(rk[i]<rk[j]) lcp(suf(i),suf(j))=min(height[rk[i]+1],height[rk[i]+2],..,height[rk[j]]),(rk[i]<rk[j])

两个串是 p p p相似的,必然也是 0 , 1 , 2 , . . , p − 1 0,1,2,..,p-1 0,1,2,..,p1相似的。所以求出每两个后缀之前最大是 p p p相似时统计如 p p p的答案后,做个后缀和即可。

将所有点按 h e i g h t height height排序,逐个枚举分界点 i i i,求出极大区间 [ l i , r i ] [l_i,r_i] [li,ri]满足 m i n k = l i r i h e i g h t [ k ] = h e i g h t [ i ] min_{k=l_i}^{r_i}height[k]=height[i] mink=liriheight[k]=height[i],分别求出 [ l i , i − 1 ] [l_i,i-1] [li,i1] [ i , r i ] [i,r_i] [i,ri]区间对应点的最大和最小美味度(记录最小是因为可能两个负数相乘得到更大的值) m x , m n mx,mn mx,mn

h e i g h t height height降序加入点,就是一个并查集的合并区间操作。


法2(SAM):
将串倒序插入 S A M SAM SAM后,按照 f a i l fail fail链连起来就得到了一颗后缀树, l c p ( i , j ) = L C A ( d e p ( c n t i , c n t j ) ) lcp(i,j)=LCA(dep(cnt_i,cnt_j)) lcp(i,j)=LCA(dep(cnti,cntj))( c n t i cnt_i cnti表示以 i i i开始的后缀对应的结点),直接在树上 d p dp dp即可。


代码

SA

#include
#define mem(f,x) memset((f),(x),sizeof((f)))
using namespace std;
const int inf=2e9;
typedef long long ll;
const int N=3e5+10;

int tk,n,val[N];char s[N];
ll v[N],f[N];

struct bcj{int fa,mn,mx,sz;}b[N];
struct ht{
   int h,x,y;
   bool operator<(const ht&ky)const{
        return ky.h<h;
   }
}q[N];

int getfa(int x){return b[x].fa==x?x:(b[x].fa=getfa(b[x].fa));}

struct SA{
	int m,sa[N],t1[N],t2[N],c[N],rk[N],h[N];
	
    inline void build()
    {
    	int i,k,p,*x=t1,*y=t2;m=26;
    	for(i=1;i<=m;++i) c[i]=0;
    	for(i=1;i<=n;++i) c[(x[i]=(int)s[i])]++;
    	for(i=1;i<=m;++i) c[i]+=c[i-1];
		for(i=n;i;--i) sa[c[x[i]]--]=i;
		for(k=1;k<n;k<<=1){
			for(p=0,i=n-k+1;i<=n;++i) y[++p]=i;
	        for(i=1;i<=n;++i) if(sa[i]>k) y[++p]=sa[i]-k;
			for(i=1;i<=m;++i) c[i]=0;
			for(i=1;i<=n;++i) c[x[y[i]]]++;
			for(i=1;i<=m;++i) c[i]+=c[i-1];
			for(i=n;i;--i) sa[c[x[y[i]]]--]=y[i];
			p=1;swap(x,y);x[sa[1]]=1;
			for(i=2;i<=n;++i){
				p+=((y[sa[i]]!=y[sa[i-1]])||(y[sa[i]+k]!=y[sa[i-1]+k]))?1:0;
				x[sa[i]]=p;
			}
			if(p>=n) break;
			m=p;
		}
    }
    inline void sol()
    {
    	int i,j,k=0,x,y;
    	for(i=1;i<=n;++i) rk[sa[i]]=i;
		for(i=1;i<=n;++i) b[i]=(bcj){i,val[sa[i]],val[sa[i]],1};
    	for(i=1;i<=n;++i){
    		if(rk[i]==1) {k=0;continue;}
			if(k) k--;j=sa[rk[i]-1];
    		for(;j+k<=n && i+k<=n && s[j+k]==s[i+k];++k);
    		h[rk[i]]=k;
    	}
    	for(i=1;i<n;++i)  q[i]=(ht){h[i+1],i,i+1};
    	sort(q+1,q+n);
    	for(i=q[1].h,j=1;~i;--i){
    		v[i]=v[i+1];f[i]=f[i+1];
    		for(;j<n && q[j].h==i;++j){
    			x=getfa(q[j].x);y=getfa(q[j].y);
    			v[i]=max(v[i],max((ll)b[x].mx*b[y].mx,(ll)b[x].mn*b[y].mn));
				f[i]+=(ll)b[x].sz*b[y].sz;b[y].fa=x;b[x].sz+=b[y].sz;
				b[x].mn=min(b[x].mn,b[y].mn);b[x].mx=max(b[x].mx,b[y].mx);
    		}
    	}
    }
}A;

int main(){
	int i;mem(v,0x8f);
	scanf("%d%s",&n,s+1);
	for(i=1;i<=n;++i) s[i]=s[i]-'a'+1; 
	for(i=1;i<=n;++i) scanf("%d",&val[i]);
	A.build();A.sol();
	for(i=0;i<n;++i) printf("%lld %lld\n",f[i],(f[i])?(v[i]):0LL);
	return 0;
}

你可能感兴趣的:(后缀自动机,后缀数组)