KMP+AC自动机

BZOJ4974 - 字符串大师

看到“循环节”会想到 KMP 某著名结论:循环节长度 \(i-nxt[i]\) . 显然这里给出了循环节长度,所以可以求出 \(nxt[i]=i-pre[i]\) .

  • \(nxt[i]\neq 0\)

直接 \(s[i]=s[nxt[i]]\) .

  • \(nxt[i]=0\)

这就意味着 \(s[i-1]\) 的任何一个 \(nxt\) 的下一个字符都和当前不同,所以往前一直跳 \(nxt\) ,并在可用字符集里去掉它的下一个位置。最后剩下的第一个字符就是字典序最小的方案。

Code
s[1]='a'; nxt[0]=-1;
for ( int i=2; i<=n; i++ )
	if ( nxt[i] ) s[i]=s[nxt[i]];
	else
	{
		memset( vis,0,sizeof(vis) );
		for ( int j=nxt[i-1]; j>=0; j=nxt[j] ) vis[s[j+1]-'a']=1;
		for ( int j=0; j<26; j++ )
			if ( !vis[j] ) { s[i]='a'+j; break; }
	}

LOJ2246 - 动物园

统计 \(nxt\) 的同时累加 \(cnt\) 表示方案数,然后再来一遍,如果发现超过一半了就直接跳 \(nxt\) 直到合法即可。

Code
for ( int i=2,j=0; i<=n; i++ )
{
	while ( j && (s[i]!=s[j+1]) ) j=nxt[j];
	if ( s[i]==s[j+1] ) j++;
	nxt[i]=j; cnt[i]=cnt[j]+1;
}
for ( int i=2,j=0; i<=n; i++ )
{
	while ( j && (s[i]!=s[j+1]) ) j=nxt[j];
	if ( s[i]==s[j+1] ) j++;
	while ( j>i/2 ) j=nxt[j];
	ans=1ll*ans*(cnt[j]+1)%Mod;
}

BZOJ4560 字符串覆盖

显然 KMP 可以求出所有出现位置。

  • 对于最大值,可以 \(n!\) 枚举放置顺序,每次选择位置开头离上一个结尾最近的即可。
  • 对于最小值,首先去掉完全重合的,因为可以把它放在重合位置上,不影响结果。然后从长到短排序,DP。设 \(dp[S][i]\) 表示放置情况为 \(S\) ,最后一个位置是 \(i\) 的覆盖总长度。当最后一个覆盖 \(i\) 的串不相交,\(dp[S][i]=\min(dp[S'][k])\) ,用前缀和优化。相交,\(dp[S][i]=\min(dp[S'][j]+i-j)\) ,单调队列优化。

谢邀,已经写吐了。

Code

Code

LOJ2004 硬币游戏

上次做这玩意儿是生成函数板题…… 所以有没有普及做法

Code

Code

LOJ2507 Matching

LOJ 题面比 BZOJ 清晰到不知道哪里去了。

题意其实就是,定义两个串相等为其离散化之后完全相同,求 \(a\)\(b\) 中相等的子串个数。

离散化可以转化为,一个位置之前比它小的个数。如果这个相等那么就是相同。然后KMP+树状数组维护这个东西即可。

Code
//Author: RingweEH
void KMP()
{
	memset( tr,0,sizeof(tr) );
	for ( int i=2,j=0; i<=n; i++ )
	{
		while ( Query(pos[i])!=val[j+1] )
		{
			for ( int k=i-j; k<i-nxt[j]; k++ ) Add(pos[k],-1); j=nxt[j];
		}
		if ( Query(pos[i])==val[j+1] ) Add(pos[i],1),j++;
		nxt[i]=j;
	}
}

int main()
{
//freopen( "exam.in","r",stdin );

	n=read(); m=read();
	for ( int i=1; i<=n; i++ ) a[i]=read(),pos[a[i]]=i;
	for ( int i=1; i<=n; i++ ) val[i]=Query(pos[i]),Add(pos[i],1);
	for ( int i=1; i<=m; i++ ) b[i]=read(),c[i]=b[i];

	KMP(); sort(c+1,c+1+m); memset( tr,0,sizeof(tr) );
	for ( int i=1,j=0; i<=m; i++ )
	{
		b[i]=lower_bound(c+1,c+1+m,b[i])-c;
		while ( j==n || Query(b[i])!=val[j+1] ) 
		{
			for ( int k=i-j; k<i-nxt[j]; k++ ) Add(b[k],-1);
			j=nxt[j];
		}
		if ( Query(b[i])==val[j+1] ) j++,Add(b[i],1);
		if ( j==n ) ans[++num]=i-j+1;
	}

	printf("%d\n",num );
	for ( int i=1; i<=num; i++ ) printf("%d ",ans[i] );

	return 0;
}

P6125 有趣的游戏

先建出 AC 自动机,标记每个位置是否是终止态和每个串所对应的终止节点。然后对于这个自动机建转移矩阵。如果是终止节点就连概率为 \(1\) 的自环,否则按概率转移到其他点,第 \(i\) 个串的答案就是 \(trans[0][id[i]]\) ,其中 \(id[i]\) 是这个串对于的终止节点。

由于要保证精度所以要把转移矩阵自乘若干遍……

Code
//Author: RingweEH
struct Matrix{}trans;
void Insert( char *s,int x )
{
	int l=strlen(s),p=1,ch;
	for ( int i=0; i<l; i++ )
	{
		ch=s[i]-'A'+1;
		if ( !tr[p][ch] ) tr[p][ch]=++tot;
		p=tr[p][ch];
	}
	pos[p]=1,id[x]=p;
}
void GetFail()
{
	int hd=0,tl=1; q[0]=1; fail[1]=0;
	while ( hd<tl )
	{
		int nw=q[hd++];
		for ( int i=1; i<=m; i++ )
		{
			if ( !tr[nw][i] ) continue; int nxt=fail[nw];
			while ( !tr[nxt][i] ) nxt=fail[nxt];
			fail[tr[nw][i]]=tr[nxt][i];
			if ( pos[tr[nxt][i]] ) pos[tr[nw][i]]=1;
			q[tl++]=tr[nw][i]; 
		}
	}
}
void GetTrans()
{
	for ( int i=1; i<=tot; i++ )
		if ( pos[i] ) trans.mat[i][i]=1;
		else
			for ( int j=1; j<=m; j++ )
			{
				int p=i;
				while ( !tr[p][j] ) p=fail[p];
				p=tr[p][j]; trans.mat[i][p]+=pro[j];
			}
}

int main()
{
//freopen( "exam.in","r",stdin );

	for ( int i=1; i<=26; i++ ) tr[0][i]=1;
	scanf("%d%d%d",&n,&l,&m);
	for ( int i=1,x,y; i<=m; i++ ) scanf("%d%d",&x,&y),pro[i]=(db)x/(db)y;
	for ( int i=1; i<=n; i++ ) scanf("%s",s),Insert(s,i);
	GetFail(); GetTrans();

	for ( int i=1; i<=50; i++ ) trans=trans*trans;
	for ( int i=1; i<=n; i++ ) printf("%.2lf\n",trans.mat[1][id[i]] );

	return 0;
}

P4045 密码

离谱题。先考虑求方案数。对所有串建立AC自动机,然后在上面跑状压DP,记录长度、当前节点和已经用掉的模板串状态。随便转移一下就好了。

考虑求方案。如果有一个字符不属于其他任何一个字符串,那么就必然要乘上 \(26\) ……而方案数不大于 \(42\) ,要么就是所有给出字符串的拼接,要么就是只有一个字符(但是 \(n\ge 1\) ,所以只可能是拼接)。

然后就这么个 \(O(n!)\) 要判一大堆东西……出题人我谢谢你。

(还有一种方式是 DP 显然…… /tuu

Code
//Author: RingweEH
namespace ACAuto
{
	const int M=110;
	int tr[M][26],fail[M],val[M],tot=0,q[110];
	void Insert( char *s,int x )
	{
		int l=strlen(s),p=0,ch;
		for ( int i=0; i<l; i++ )
		{
			ch=s[i]-'a';
			if ( !tr[p][ch] ) tr[p][ch]=++tot;
			p=tr[p][ch];
		}
		val[p]=1<<x;
	}
	void GetFail()
	{
		//queue<int> q;
		int head=0,tail=0;
		for ( int i=0; i<26; i++ )
			if ( tr[0][i] ) q[++tail]=tr[0][i];//q.push(tr[0][i]);
		while ( head<tail )
		{
			int nw=q[++head]; //q.pop();
			for ( int i=0; i<26; i++ )
				if( tr[nw][i] )
				{
					fail[tr[nw][i]]=tr[fail[nw]][i];
					q[++tail]=tr[nw][i];//q.push(tr[nw][i]);
				}
				else tr[nw][i]=tr[fail[nw]][i];
			val[nw]|=val[fail[nw]];
		}
	}
}
using namespace ACAuto;

namespace GetPath
{
	bool vis[26][110][1<<10],g[26][110][1<<10],lim;
	int que[26];
	bool DFS( int i,int j,int S )
	{
		if ( i==len ) { vis[i][j][S]=1; return g[i][j][S]=(S==((1<<n)-1)); }
		bool fl=0;
		if ( vis[i][j][S] ) return g[i][j][S];
		else vis[i][j][S]=1;
		for ( int ch=0; ch<26; ch++ )
			if ( tr[j][ch] ) fl|=DFS(i+1,tr[j][ch],S|val[tr[j][ch]]);
		return g[i][j][S]=fl;
	}
	void Write( int i,int j,int S )
	{
		if ( !g[i][j][S] ) return;
		if ( i==len )
		{
			for ( int ch=1; ch<=len; ch++ ) putchar(que[ch]+'a');
			puts(""); return;
		}
		for ( int ch=0; ch<26; ch++ )
			que[i+1]=ch,Write(i+1,tr[j][ch],S|val[tr[j][ch]]);
	}
}

int main()
{
//freopen( "exam.in","r",stdin );

	scanf("%d%d",&len,&n);
	for ( int i=0; i<n; i++ ) scanf("%s",s),Insert(s,i);

	GetFail(); dp[0][0][0]=1; int lim=(1<<n);
	for ( int i=1; i<=len; i++ )
		for ( int j=0; j<=tot; j++ )
			for ( int S=0; S<lim; S++ )
				if ( dp[i-1][j][S] )
					for ( int k=0; k<26; k++ )
						dp[i][tr[j][k]][S|val[tr[j][k]]]+=dp[i-1][j][S];

	ll ans=0;
	for ( int i=0; i<=tot; i++ ) ans+=dp[len][i][lim-1];
	printf("%lld\n",ans );
	if ( ans>42 ) return 0;
	GetPath::DFS(0,0,0); 
	GetPath::Write(0,0,0);

	return 0;
}

LOJ2180 魔法咒语

数 据 分 治

前面 \(L\leq 100\) 的可以 AC自动机上 DP;后面长度 \(\leq 2\) 需要矩乘。

调了半年……结论:写矩乘要写 *=void ,不然矩阵一大就炸空间……

Code

Code

posted @ 2021-05-18 20:11  MontesquieuE  阅读(84)  评论(0)    收藏  举报