zoukankan      html  css  js  c++  java
  • 循环串

    循环串,常常和(border)有关。

    一个字符串的(border)可以拆分为(O(logn))个等差数列,这个性质常常用于(DP)优化。

    例题1:回文拆分

    这两道题,都可以通过一些转化变为回文区间划分。

    首先,建立回文树。

    使用(DP):设(f(i))表示前(i)个字符的回文拆分。设前(i)个字符的最长回文后缀为(u)

    转移时,枚举(u)在回文树上的祖先,暴力进行转移。

    这样的复杂度是最坏(O(n^2))的,因为如果字符串有大量循环节(比如全是a)会导致回文树深度很大。

    根据回文的对称性,一个回文的回文后缀也是他的一个(border)

    因此,根据(border)的性质,回文后缀的长度可以划分为(O(logn))个等差数列。依次转移即可。

    设这个数列的公差为(d)。那么不难发现,在一个节点的父节点用这个等差数列转移时,涉及到的(f(j))刚好比当前所需要的少一个。

    那么,对每个树上的节点记录一个(g),表示上次用这个节点进行等差数列的转移总和。

    这次转移时,把缺少的一项(就是利用这个等差数列中最短的进行转移的那一项)补上,并更新(g)值即可。

    每次转移(f)时都向上跳一个等差数列,复杂度就是对的了。这个可以预处理。

    注意不要算重复,同时在在数列长度为1时无需转移。

    代码:

    int insert(int i,char c)//构建回文树
    {
    	int x=int(c-'a');
    	while(i-len[la]-1<=0||zf[i-len[la]-1]!=c)
    		la=fa[la];
    	if(trs[la][x])
    	{
    		la=trs[la][x];
    		return la;
    	}
    	int w=trs[la][x]=++sl,t=fa[la];len[w]=len[la]+2;
    	while(t&&(trs[t][x]==0||i-len[t]-1<=0||zf[i-len[t]-1]!=c))
    		t=fa[t];
    	if(trs[t][x])
    	{
    		int f=trs[t][x];
    		cz[w]=len[w]-len[f];//差值
    		if(cz[w]==cz[f])
    			tp[w]=tp[f];
    		else
    			tp[w]=f;//预处理跳到的位置
    		fa[w]=f;
    	}
    	else 
    	{
    		tp[w]=fa[w]=2;
    		cz[w]=len[w];
    	}
    	return la=w;
    }
    

    状态转移:

    	for(int i=1;i<=n;i++)
    	{
    		int u=wz[i];
    		while(u!=2)
    		{
    			he[u]=dp[i-len[tp[u]]-cz[u]];//补上最短的那个
    			if(tp[u]!=fa[u])//用父节点记录的值转移
    				he[u]=(he[u]+he[fa[u]])%md;
    			if(i%2==0)//这道题目只用偶串转移
    				dp[i]=(dp[i]+he[u])%md;
    			u=tp[u];
    		}
    	}
    

    例题2:论战捆竹竿

    题意:问一个字符串的所有(border)能相加凑成多少不同的数。

    由于是凑数问题,因此考虑同余最短路。直接做边数太多显然超时。

    仍然将这些数划分为若干等差数列,分别添加。

    设等差数列为(d,d+x,d+2x,……,d+kx)

    先把模数修改为(d)。注意修改时要在每个节点上添加一条权值为原来模数的边。

    这样,每个节点都连上(k)条边。

    根据数论知识,模(gcd(d,x))不同的点互不影响。这样就变为了(gcd(d,x))个环。

    每个环从距离最小的位置拆为链,这样就是每个点向一段区间连边。

    使用单调队列优化转移即可。时间复杂度(O(nlogn))

    代码:

    void chmod(int zz)
    {
    	for(int i=0;i<zz;i++)
    		jh[i]=inf;
    	jh[0]=0;
    	for(int i=0;i<md;i++)
    	{
    		ll z=jl[i];
    		if(z<inf&&z<jh[z%zz])
    			jh[z%zz]=z;
    	}
    	int g=gcd(md,zz);
    	for(int i=0;i<zz;i++)
    		jl[i]=jh[i];
    	for(int s=0;s<g;s++)
    	{
    		int i=s,w=s;
    		do
    		{
    			if(jl[i]<jl[w])w=i;
    			i=(i+md)%zz;
    		}while(i!=s);
    		for(i=(w+md)%zz;i!=w;i=(i+md)%zz)
    		{
    			ll t=jl[(i-md%zz+zz)%zz]+md;
    			if(t<jl[i])jl[i]=t;
    		}
    	}
    	md=zz;
    }
    int dl[500010],he,ta,bh[500010];ll qz[500010];
    void insert(int x)
    {
    	while(he<ta&&qz[x]<=qz[dl[ta-1]])
    		ta-=1;
    	dl[ta++]=x;
    }
    void del(int x)
    {
    	if(he<ta&&dl[he]==x)he+=1;
    }
    void addsl(int d,int x,int k)
    {
    	chmod(d);
    	if(k==0)return;
    	int g=gcd(md,x);
    	for(int s=0;s<g;s++)
    	{
    		int i=s,w=s;
    		do
    		{
    			if(jl[i]<jl[w])w=i;
    			i=(i+x)%md;
    		}while(i!=s);
    		for(int i=w,t=0;t<md/g;i=(i+x)%md,t++)bh[i]=t;
    		he=ta=0;qz[w]=jl[w];
    		for(i=(w+x)%md;i!=w;i=(i+x)%md)
    		{
    			insert((i-x+md)%md);
    			del((i-(k+1)*x%md+md)%md);
    			if(he<ta)
    			{
    				ll t=qz[dl[he]]+1ll*bh[i]*x+d;
    				if(t<jl[i])jl[i]=t;
    			}
    			qz[i]=jl[i]-1ll*bh[i]*x;
    		}
    	}
    }
    

    一个字符串的所有整周期循环子串,是可以在(O(nlogn))时间内枚举的。

    首先,定义本原平方串为所有形如(AA)的串。其中(A)不是整周期循环串。

    所有本原平方串的个数是(O(nlogn))的。本质不同的本原平方串的个数是(O(n))的。

    暴力枚举所有本原平方串:

    使用这个做法。

    先枚举长度L,然后隔L放置关键点,求出相邻关键点的(lcp,lcs)得出长度为(L)的平方串的开头位置,将这些位置划分为若干区间。这个可以用后缀数组优化。

    对于每个区间([l,r]),若([l,l+L-1])这个串循环,那么就忽略这个区间。

    否则,枚举(L)的倍数(q),满足(lleq r+2L-2q),将([l,l+q-1])这个串标记为循环。

    枚举(lleq ileq r),就得到了所有本原平方串。它在这个区间的最大循环次数为(frac{r-i}{L}+2)

    使用哈希表去重即可得到本质不同的。

    得到所有本原平方串和他们的重复次数就得到了所有循环串。

    例题:

    超级毒瘤题。求本质不同的双回文串子串个数。

    先只考虑本质不同。对于一个位置他的前后缀的回文前后缀可以拼成一个。使用线段树合并去重。

    具体方法见 P5327 [ZJOI2019]语言

    然后,有多种切分方案的双回文串一定是循环串。枚举出来去重即可。

    代码7k,不放了。

    先根据每个串是否等于下一个来容斥。转移时枚举循环串即可,方法同上。

  • 相关阅读:
    领域驱动设计ddd
    LayUI
    Kendo框架
    mysql rdms 笔记
    window系统安装mysql
    在VS项目中通过GIT生成版本号作为编译版本号
    在VS项目中使用SVN版本号作为编译版本号
    Oracle与SQL SERVER编程差异分析(入门)
    发布MeteoInfo 3.0
    Tomcat7 安全部署配置修改
  • 原文地址:https://www.cnblogs.com/lnzwz/p/14623977.html
Copyright © 2011-2022 走看看