• [NOI2015]品酒大会


    XVI.[NOI2015]品酒大会

    我居然能自己AC NOI的原题,后缀数组果然简单

    首先当然是轻松建出SA。

    我们考虑借鉴XII.[TJOI2015]弦论的思想,建出笛卡尔树。则对于当前的\(ht\)长度,它出现在了\((l,r)\)区间里的每一个后缀里,共计\(\dfrac{(r-l+2)(r-l+1)}{2}\)次。同时,这份贡献会对\((ht_{(l,r)},ht_{fa})\)区间中的每一个”\(r\)相似“全部有这么多的贡献,因此直接差个分即可。同时,这个区间内部任意两个\(a_{sa_i}\times a_{sa_j}\)\(\max\),要么来自于(\(\text{最大}\times\text{次大}\)),要么来自于(\(\text{最小}\times\text{次小}\)),用线段树维护即可。最后,对差分数组做后缀和,对\(\max\)数组求后缀\(\max\)即可。

    复杂度\(O(n\log n)\),TLE。

    代码:

    #pragma GCC optimize(3)
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,m,a[300100];
    int x[300100],y[300100],sa[300100],ht[300100],rk[300100],buc[300100];
    char s[300100];
    bool mat(int a,int b,int k){
    	if(y[a]!=y[b])return false;
    	if((a+k<n)^(b+k<n))return false;
    	if((a+k<n)&&(b+k<n))return y[a+k]==y[b+k];
    	return true;
    }
    void SA(){
    	for(int i=0;i<n;i++)buc[x[i]=s[i]]++;
    	for(int i=1;i<=m;i++)buc[i]+=buc[i-1];
    	for(int i=n-1;i>=0;i--)sa[--buc[x[i]]]=i;
    	for(int k=1;k<n;k<<=1){
    		int num=0;
    		for(int i=n-k;i<n;i++)y[num++]=i;
    		for(int i=0;i<n;i++)if(sa[i]>=k)y[num++]=sa[i]-k;
    		for(int i=0;i<=m;i++)buc[i]=0;
    		for(int i=0;i<n;i++)buc[x[y[i]]]++;
    		for(int i=1;i<=m;i++)buc[i]+=buc[i-1];
    		for(int i=n-1;i>=0;i--)sa[--buc[x[y[i]]]]=y[i];
    		swap(x,y);
    		x[sa[0]]=num=0;
    		for(int i=1;i<n;i++)x[sa[i]]=mat(sa[i],sa[i-1],k)?num:++num;
    		m=num;
    	}
    	for(int i=0;i<n;i++)rk[sa[i]]=i;
    	for(int i=0,k=0;i<n;i++){
    		if(!rk[i])continue;
    		if(k)k--;
    		int j=sa[rk[i]-1];
    		while(i+k<n&&j+k<n&&s[i+k]==s[j+k])k++;
    		ht[rk[i]]=k;
    	}
    }
    int LG[300100],mn[300100][20];
    ll cnt[300100],mx[300100];
    int MIN(int x,int y){return ht[x]<=ht[y]?x:y;}
    int RMQ(int l,int r){
    	int k=LG[r-l+1];
    	return MIN(mn[l][k],mn[r-(1<<k)+1][k]);
    }
    #define lson x<<1
    #define rson x<<1|1
    #define mid ((l+r)>>1)
    struct SegTree{
    	int mx1,mx2,mn1,mn2;
    	SegTree(){mx1=mx2=0x80808080,mn1=mn2=0x3f3f3f3f;}
    	SegTree(int x){mx1=mn1=x,mx2=0x80808080,mn2=0x3f3f3f3f;}
    	friend SegTree operator +(const SegTree &x,const SegTree &y){
    		SegTree z;
    		vector<int>v;
    		v={x.mx1,x.mx2,y.mx1,y.mx2};
    		sort(v.begin(),v.end());
    		z.mx1=v[3],z.mx2=v[2];
    		v={x.mn1,x.mn2,y.mn1,y.mn2};
    		sort(v.begin(),v.end());
    		z.mn1=v[0],z.mn2=v[1];
    		return z;
    	}
    	ll calc(){
    		ll ret=0x8080808080808080;
    		if(mn1!=0x3f3f3f3f&&mn2!=0x3f3f3f3f)ret=max(ret,1ll*mn1*mn2);
    		if(mx1!=0x80808080&&mx2!=0x80808080)ret=max(ret,1ll*mx1*mx2);
    		return ret;
    	}
    }seg[1200100];
    void build(int x,int l,int r){
    	if(l==r){seg[x]=SegTree(a[sa[l]]);return;}
    	build(lson,l,mid),build(rson,mid+1,r),seg[x]=seg[lson]+seg[rson]; 
    }
    SegTree query(int x,int l,int r,int L,int R){
    	if(l>R||r<L)return SegTree();
    	if(L<=l&&r<=R)return seg[x];
    	return query(lson,l,mid,L,R)+query(rson,mid+1,r,L,R);
    }
    void solve(int l,int r,int las){
    	if(l>r)return;
    	int mp=RMQ(l,r),len=r-l+2;
    	cnt[ht[mp]]+=1ll*len*(len-1)/2,mx[ht[mp]]=max(mx[ht[mp]],query(1,0,n-1,l-1,r).calc());
    	cnt[las]-=1ll*len*(len-1)/2;
    	solve(l,mp-1,ht[mp]),solve(mp+1,r,ht[mp]);
    }
    int main(){
    	scanf("%d%s",&n,s),m='z',memset(mx,0x80,sizeof(mx));
    	for(int i=0;i<n;i++)scanf("%d",&a[i]);
    	SA();
    	for(int i=2;i<n;i++)LG[i]=LG[i>>1]+1;
    	for(int i=1;i<n;i++)mn[i][0]=i;
    	for(int j=1;j<=LG[n-1];j++)for(int i=1;i+(1<<j)-1<n;i++)mn[i][j]=MIN(mn[i][j-1],mn[i+(1<<(j-1))][j-1]);
    	build(1,0,n-1),solve(1,n-1,-1);
    	for(int i=n-1;i>=0;i--)cnt[i]+=cnt[i+1],mx[i]=max(mx[i],mx[i+1]);
    	for(int i=0;i<n;i++)printf("%lld %lld\n",cnt[i],cnt[i]==0ll?0ll:mx[i]);
    	return 0;
    }
    

    既然如此,我们只能考虑\(O(n)\)地建笛卡尔树并\(O(n)\)维护最大次大值。

    在经历多次失败后,我找出了\(O(n)\)建树的方法:

    for(int i=1;i<n;i++){
    	while(tp&&ht[stk[tp]]>ht[i])R[stk[tp]]=i,lson[i]=stk[tp],tp--;
    	L[i]=stk[tp],rson[stk[tp]]=i,stk[++tp]=i;
    }
    while(tp)R[stk[tp--]]=n;
    rt=stk[1];
    solve(rt,-1);
    

    但是怎样\(O(n)\)维护呢?

    又经过实验后,我发现这个最大次大值可以通过两个儿子的值合并算出来。如果某个儿子不存在的话,就要把对应的端点贡献算入。

    这里是\(O(n)\)solve函数:

    void solve(int x,int las){
    	int len=R[x]-L[x];
    	cnt[ht[x]]+=1ll*len*(len-1)/2;
    	cnt[las]-=1ll*len*(len-1)/2;
    	if(lson[x])solve(lson[x],ht[x]),dat[x]+=dat[lson[x]];
    	else dat[x]+=Data(a[sa[L[x]]]);
    	if(rson[x])solve(rson[x],ht[x]),dat[x]+=dat[rson[x]];
    	else dat[x]+=Data(a[sa[R[x]-1]]);
    	mx[ht[x]]=max(mx[ht[x]],dat[x].calc());
    }
    

    其中dat[x]是一个类型为Datastruct,已经提前定义了加法,可以维护最大次大值。

    总复杂度\(O(n\log n)\),瓶颈为后缀排序,采用DC3算法即可优化到整体\(O(n)\)。但是不使用DC3仍然可以通过。

    代码:

    #pragma GCC optimize(3)
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n,m,a[300100];
    int x[300100],y[300100],sa[300100],ht[300100],rk[300100],buc[300100];
    char s[300100];
    bool mat(int a,int b,int k){
    	if(y[a]!=y[b])return false;
    	if((a+k<n)^(b+k<n))return false;
    	if((a+k<n)&&(b+k<n))return y[a+k]==y[b+k];
    	return true;
    }
    void SA(){
    	for(int i=0;i<n;i++)buc[x[i]=s[i]]++;
    	for(int i=1;i<=m;i++)buc[i]+=buc[i-1];
    	for(int i=n-1;i>=0;i--)sa[--buc[x[i]]]=i;
    	for(int k=1;k<n;k<<=1){
    		int num=0;
    		for(int i=n-k;i<n;i++)y[num++]=i;
    		for(int i=0;i<n;i++)if(sa[i]>=k)y[num++]=sa[i]-k;
    		for(int i=0;i<=m;i++)buc[i]=0;
    		for(int i=0;i<n;i++)buc[x[y[i]]]++;
    		for(int i=1;i<=m;i++)buc[i]+=buc[i-1];
    		for(int i=n-1;i>=0;i--)sa[--buc[x[y[i]]]]=y[i];
    		swap(x,y);
    		x[sa[0]]=num=0;
    		for(int i=1;i<n;i++)x[sa[i]]=mat(sa[i],sa[i-1],k)?num:++num;
    		m=num;
    	}
    	for(int i=0;i<n;i++)rk[sa[i]]=i;
    	for(int i=0,k=0;i<n;i++){
    		if(!rk[i])continue;
    		if(k)k--;
    		int j=sa[rk[i]-1];
    		while(i+k<n&&j+k<n&&s[i+k]==s[j+k])k++;
    		ht[rk[i]]=k;
    	}
    }
    struct Data{
    	int mx1,mx2,mn1,mn2;
    	Data(){mx1=mx2=0x80808080,mn1=mn2=0x3f3f3f3f;}
    	Data(int x){mx1=mn1=x,mx2=0x80808080,mn2=0x3f3f3f3f;}
    	void Print()const{
    		printf("%d %d %d %d\n",mx1,mx2,mn1,mn2);
    	}
    	void operator +=(const Data&x){
    		if(x.mx1>=mx1)mx2=max(mx1,x.mx2),mx1=x.mx1;
    		else mx2=max(mx2,x.mx1);
    		if(x.mn1<=mn1)mn2=min(mn1,x.mn2),mn1=x.mn1;
    		else mn2=min(mn2,x.mn1);
    	}
    	ll calc(){
    		ll ret=0x8080808080808080;
    		if(mn1!=0x3f3f3f3f&&mn2!=0x3f3f3f3f)ret=max(ret,1ll*mn1*mn2);
    		if(mx1!=0x80808080&&mx2!=0x80808080)ret=max(ret,1ll*mx1*mx2);
    		return ret;
    	}
    }dat[300100];
    int stk[300100],tp,lson[300100],rson[300100],L[300100],R[300100],rt;
    ll cnt[300100],mx[300100];
    void solve(int x,int las){
    	int len=R[x]-L[x];
    	cnt[ht[x]]+=1ll*len*(len-1)/2;
    	cnt[las]-=1ll*len*(len-1)/2;
    	if(lson[x])solve(lson[x],ht[x]),dat[x]+=dat[lson[x]];
    	else dat[x]+=Data(a[sa[L[x]]]);
    	if(rson[x])solve(rson[x],ht[x]),dat[x]+=dat[rson[x]];
    	else dat[x]+=Data(a[sa[R[x]-1]]);
    //	printf("%d:(%d,%d):(%d,%d):%d\n",x,L[x],R[x]-1,lson[x],rson[x],ht[x]);
    //	printf("%d:",x);dat[x].Print();
    	mx[ht[x]]=max(mx[ht[x]],dat[x].calc());
    }
    int main(){
    	scanf("%d%s",&n,s),m='z',memset(mx,0x80,sizeof(mx));
    	for(int i=0;i<n;i++)scanf("%d",&a[i]);
    	SA();
    	for(int i=1;i<n;i++){
    		while(tp&&ht[stk[tp]]>ht[i])R[stk[tp]]=i,lson[i]=stk[tp],tp--;
    		L[i]=stk[tp],rson[stk[tp]]=i,stk[++tp]=i;
    	}
    	while(tp)R[stk[tp--]]=n;
    //	for(int i=0;i<n;i++)printf("%d ",a[sa[i]]);puts("");
    	rt=stk[1];
    	solve(rt,-1);
    	for(int i=n-1;i>=0;i--)cnt[i]+=cnt[i+1],mx[i]=max(mx[i],mx[i+1]);
    	for(int i=0;i<n;i++)printf("%lld %lld\n",cnt[i],cnt[i]==0ll?0ll:mx[i]);
    	return 0;
    }
    

  • 相关阅读:
    1-6.webpack对字体图标和图片的处理
    1-5.Webpack对Html模板处理
    1-4.Webpack对样式的处理
    1-3.Webpack对脚本的处理
    1-2.Webpack初始化
    1-1.mmall电商项目搭建
    Java 随心笔记11
    Java 随心笔记10
    Java 随心笔记9
    Java 随心笔记8
  • 原文地址:https://www.cnblogs.com/Troverld/p/14605247.html
Copyright © 2020-2023  润新知