zoukankan      html  css  js  c++  java
  • 【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)

    【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)

    题面

    BZOJ
    Uoj

    题解

    如果我们知道以某个位置为开始/结尾的(AA)串的个数
    那就直接做一下乘法就好
    这个怎么求?
    枚举一个位置
    枚举串的长度
    直接暴力算就好啦
    至于是否可行,用(SA)(lcp)就好啦
    这样就是(95)
    NOI这么好拿部分分的???

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define ll long long
    #define MAX 35000
    int x[MAX],y[MAX],t[MAX];
    int SA[MAX],height[MAX],rk[MAX];
    int lg[MAX],n,p[20][MAX],a[MAX];
    char s[MAX];
    int g[MAX],f[MAX],T;
    bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
    void init()
    {
    	memset(SA,0,sizeof(SA));
    	memset(height,0,sizeof(height));
    	memset(rk,0,sizeof(rk));
    	memset(x,0,sizeof(x));
    	memset(y,0,sizeof(y));
    	memset(t,0,sizeof(t));
    	memset(a,0,sizeof(a));
    }
    void GetSA()
    {
    	int m=50;
    	for(int i=1;i<=n;++i)t[x[i]=a[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 p=0;
    		for(int i=n-k+1;i<=n;++i)y[++p]=i;
    		for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
    		for(int i=0;i<=m;++i)t[i]=0;
    		for(int i=1;i<=n;++i)t[x[y[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];
    		swap(x,y);
    		x[SA[1]]=p=1;
    		for(int i=2;i<=n;++i)
    			x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
    		if(p>=n)break;
    		m=p;
    	}
    	for(int i=1;i<=n;++i)rk[SA[i]]=i;
    	for(int i=1,j=0;i<=n;++i)
    	{
    		if(j)--j;
    		while(a[i+j]==a[SA[rk[i]-1]+j])++j;
    		height[rk[i]]=j;
    	}
    }
    void Pre()
    {
    	memset(p,63,sizeof(p));
    	for(int i=1;i<=n;++i)p[0][i]=height[i];
    	for(int j=1;j<15;++j)
    		for(int i=1;i<=n;++i)
    			p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
    }
    int Query(int i,int j)
    {
    	return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
    }
    int lcp(int i,int j)
    {
    	int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
    	return Query(l,r);
    }
    int main()
    {
    	for(int i=2;i<=30000;++i)lg[i]=lg[i>>1]+1;
    	scanf("%d",&T);
    	while(T--)
    	{
    		init();
    		scanf("%s",s+1);
    		n=strlen(s+1);
    		for(int i=1;i<=n;++i)a[i]=s[i]-96;
    		GetSA();Pre();
    		for(int i=1;i<=n;++i)
    		{
    			g[i]=0;
    			for(int l=1;l+l+i-1<=n;++l)
    				if(lcp(i,i+l)>=l)g[i]++;
    		}
    		for(int i=2;i<=n;++i)
    		{
    			f[i]=0;
    			for(int l=1;i-l-l+1>0;++l)
    				if(lcp(i-l-l+1,i-l+1)>=l)f[i]++;
    		}
    		int ans=0;
    		for(int i=1;i<n;++i)
    			ans+=f[i]*g[i+1];
    		printf("%d
    ",ans);
    	}
    	return 0;
    }
    
    

    (95)分的暴力太显然了。。
    原来(NOI)都是这样送分???
    为什么NOIP 没有这么好的福利


    想想怎么优化吧。。。
    肯定不能枚举长度之后再暴力算每一个位置
    那么,我们要考虑一个方法,
    可以一次性算出连续的位置

    想想我们怎么求(AA)这种形式??
    计算(lcp(i,i+len)>=len)是否成立
    但是,如果(lcp(i,i+len)>=len)
    我们就会发现,有一段区间内都是有满足条件的子串
    所以我们可以一起计算

    现在仔细思考怎么算
    因为每次是(i)(i+len)
    所以我们只要枚举位置是(len)的倍数的地方就好
    旁边的地方我们要想办法算出来
    第一个,是向后如果可以增加的话
    (lcp(i,i+len)>=L)我就会获得向后的一段连续区间
    如果只算向后,会忽略掉向前的一段
    所以再算一下(lcs(i,i+len))这段,这两边拼起来
    如果满足条件,证明这一段区间都是可行的
    这样就可以差分全部(+1)
    如果重复的部分够多
    这样算可能会影响到别的块里面
    所以要强制只在自己这一段里面算
    具体的实现看代码啦

     #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<set>
    #include<map>
    #include<vector>
    #include<queue>
    using namespace std;
    #define ll long long
    #define MAX 35000
    int lg[MAX],n;
    char s[MAX];
    int g[MAX],f[MAX],T;
    struct SA
    {
    	int p[20][MAX],a[MAX];
    	int x[MAX],y[MAX],t[MAX];
    	int SA[MAX],height[MAX],rk[MAX];
    	bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
    	void init()
    		{
    			memset(SA,0,sizeof(SA));
    			memset(height,0,sizeof(height));
    			memset(rk,0,sizeof(rk));
    			memset(x,0,sizeof(x));
    			memset(y,0,sizeof(y));
    			memset(t,0,sizeof(t));
    			memset(a,0,sizeof(a));
    		}
    	void GetSA()
    		{
    			int m=50;
    			for(int i=1;i<=n;++i)t[x[i]=a[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 p=0;
    				for(int i=n-k+1;i<=n;++i)y[++p]=i;
    				for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
    				for(int i=0;i<=m;++i)t[i]=0;
    				for(int i=1;i<=n;++i)t[x[y[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];
    				swap(x,y);
    				x[SA[1]]=p=1;
    				for(int i=2;i<=n;++i)
    					x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
    				if(p>=n)break;
    				m=p;
    			}
    			for(int i=1;i<=n;++i)rk[SA[i]]=i;
    			for(int i=1,j=0;i<=n;++i)
    			{
    				if(j)--j;
    				while(a[i+j]==a[SA[rk[i]-1]+j])++j;
    				height[rk[i]]=j;
    			}
    		}
    	void Pre()
    		{
    			memset(p,63,sizeof(p));
    			for(int i=1;i<=n;++i)p[0][i]=height[i];
    			for(int j=1;j<15;++j)
    				for(int i=1;i<=n;++i)
    					p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
    		}
    	int Query(int i,int j)
    		{
    			return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
    		}
    	int lcp(int i,int j)
    		{
    			int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
    			return Query(l,r);
    		}
    }A,B;
    int main()
    {
    	for(int i=2;i<=30000;++i)lg[i]=lg[i>>1]+1;
    	scanf("%d",&T);
    	while(T--)
    	{
    		A.init();B.init();
    		scanf("%s",s+1);
    		n=strlen(s+1);
    		for(int i=1;i<=n;++i)A.a[i]=s[i]-96;
    		for(int i=1;i<=n;++i)B.a[n-i+1]=s[i]-96;
    		A.GetSA();A.Pre();B.GetSA();B.Pre();
    		for(int i=1;i<=n;++i)g[i]=f[i]=0;
    		for(int len=1;len<=n/2;++len)
    		{
    			for(int i=len,j=i+len;j<=n;i+=len,j+=len)
    			{
    				int x=min(A.lcp(i,j),len);
    				int y=min(B.lcp(n-i+2,n-j+2),len-1);
    				int t=x+y-len+1;
    				if(x+y>=len)
    				{
    					g[i-y]++;g[i-y+t]--;
    					f[j+x-t]++;f[j+x]--;
    				}
    			}
    		}
    		
    		for(int i=1;i<=n;++i)g[i]+=g[i-1];
    		for(int i=1;i<=n;++i)f[i]+=f[i-1];
    		ll ans=0;
    		for(int i=1;i<n;++i)
    			ans+=1ll*f[i]*g[i+1];
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    eclipse对项目Working Sets整理分类
    word中visio只显示边框,不显示内容解决
    使用WebStorm运行vue项目
    如何提高你的学习速度-超链接式学习法
    SQL中的join连接查询
    TCP的三次握手
    Tomcat 实现热部署
    Linux下软件设成系统服务运行
    Redis服务器搭建
    nginx.conf完整配置实例
  • 原文地址:https://www.cnblogs.com/cjyyb/p/8358224.html
Copyright © 2011-2022 走看看