zoukankan      html  css  js  c++  java
  • [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;
    }
    

  • 相关阅读:
    HDU 1010 Tempter of the Bone(DFS剪枝)
    HDU 1013 Digital Roots(九余数定理)
    HDU 2680 Choose the best route(反向建图最短路)
    HDU 1596 find the safest road(最短路)
    HDU 2072 单词数
    HDU 3790 最短路径问题 (dijkstra)
    HDU 1018 Big Number
    HDU 1042 N!
    NYOJ 117 求逆序数 (树状数组)
    20.QT文本文件读写
  • 原文地址:https://www.cnblogs.com/Troverld/p/14605247.html
Copyright © 2011-2022 走看看