经典AC自动机DP

CF808G Anthem of Berland

给定 \(s,t\) 串,\(s\) 串中有问号,问 \(t\)\(s\) 中的最大出现次数。

\(|s|\times |t| \leq 10^7\)

题解

AC自动机经典题,直接 \(dp(i,j)\) 表示前 \(i\) 个点,AC自动机状态是 \(j\) 的最大出现次数。

枚举出边转移就完事。时间复杂度 \(O(26nm)\)

有个常数优化,可以把 \(O(26)\) 去掉。

注意到AC自动机每个状态的出边只有一条能转移到儿子,其余的都是沿着fail跳了若干步再转移。

那么我们可以把跳fail的过程放到DP里。方法是每次chkmax(dp[i-1][fail[j]],dp[i-1][j])然后只转移那条能到儿子的边。

CO int N=1e5+10;
char s[N],t[N];
int fa[N],dp[N];

int main(){
	scanf("%s",s+1);
	int n=strlen(s+1);
	scanf("%s",t+1);
	int m=strlen(t+1);
	for(int i=2;i<=m;++i){
		int j=fa[i-1];
		while(j and t[j+1]!=t[i]) j=fa[j];
		fa[i]=j+(t[j+1]==t[i]);
	}
	fill(dp,dp+m+1,-1),dp[0]=0;
	for(int i=1;i<=n;++i){
		for(int j=m;j>=0;--j)if(dp[j]!=-1){
			if(j>0) dp[fa[j]]=max(dp[fa[j]],dp[j]);
			if(j<m and (s[i]=='?' or s[i]==t[j+1])) dp[j+1]=dp[j];
			if(j>0) dp[j]=-1;
		}
		if(dp[m]!=-1) ++dp[m];
	}
	printf("%d\n",*max_element(dp,dp+m+1));
	return 0;
}

密码

众所周知,密码在信息领域起到了不可估量的作用。对于普通的登陆口令,唯一的破解 方法就是暴力破解一逐个尝试所有可能的字母组合,但这是一项很耗时又容易被发现的工 作。所以,为了获取对方的登陆口令,在暴力破解密码之前,必须先做大量的准备工作。经 过情报的搜集,现在得到了若干有用信息,形如:

“我观察到,密码中含有字符串***。”

例如,对于一个10位的密码以及串hello与world,可观察到的字符能的密码组合为 helloworld与worldhello;而对于6位的密码以及观察到的字符串good与day,可能的 密码组合为gooday。

有了这些信息,就能够大大地减少尝试的次数了。请编一个程序,计算所有密码组合的可能。密码中仅可能包含a - z之间的小写字母。

对于100%的数据,1<=L<=25,1<=N<=10,每个观察到的字符串长不超过10,并且保证输出结果小于2^63。

LadyLex的题解

我们首先考虑:对于串\(i\)\(j\),如果\(j\)\(i\)的子串,那么我们根本不用考虑最初单独插入进来的\(j\)串,因为只要\(i\)串存在,\(j\)串就一定存在

那么我们可以在构建出AC自动机之后,把每个节点从fail指针能达到的节点都设为”不是单词节点“,最后再给单词节点重新编号即可。

那么接下来,我们考虑dp的过程。由于节点数,串数和串长都很小,所以我们考虑状态压缩来解决这个问题。

我们定义状态数组\(f[i][j][k]\)表示当前串长为\(i\),位于\(j\)号节点,模式串出现情况为\(k\)的方案数。

(这种"走\(i\)步到达\(j\)节点”也是AC自动机上的常见套路之一)

那么我们事先把单词节点对应的串用二进制压好,转移到时候我们只需要这样处理即可:

f[i+1][ch[j][u]][k|val[ch[j][u]]]+=f[i][j][k];

这样我们就可以搜出方案数,接下来我们考虑输出小于42的具体方案。

首先我们可以得到一个性质:若总方案数不超过42,那么最终串一定仅由给定串拼接而成。

因为如果随机字母可以存在,哪怕只有1个模式串,并且仅有1个随机字母,合法方案数在这种最小情况下也有2×26=52种>42

因此我们只需要用搜索进行一个dp的逆过程,看合法方案由哪个节点转移过来,并且记录一路上经过的字符,最后排序输出即可。

这真是一道很的题目,方式以及套路很经典,对于状压和搜索的应用都很灵活!
UPD:这题强行组合了两种套路。

时间复杂度\(O(26 L N^2 2^N)\),算出来是7e7。

co int N=11,L=26,K=(1<<10)+10;
int l,n;
char s[N][N];
namespace AC
{
	int tot,num;
	int ch[N*N][26],fail[N*N];
	int val[N*N],meaning[N*N];
	
	void ins(char s[],int n)
	{
		int u=0;
		for(int i=0;i<n;++i)
		{
			int k=s[i]-'a';
			if(!ch[u][k])
				ch[u][k]=++tot;
			u=ch[u][k],meaning[u]=k;
		}
		val[u]=1;
	}
	
	void getfail()
	{
		std::queue<int>Q;
		for(int i=0;i<26;++i)
			if(ch[0][i])
				Q.push(ch[0][i]);
		while(Q.size())
		{
			int u=Q.front();Q.pop();
			for(int i=0;i<26;++i)
			{
				if(ch[u][i])
				{
					fail[ch[u][i]]=ch[fail[u]][i];
					Q.push(ch[u][i]);
				}
				else
					ch[u][i]=ch[fail[u]][i];
			}
		}
		for(int i=1;i<=tot;++i)
			for(int u=fail[i];u;u=fail[u])
				val[u]=0;
		for(int i=1;i<=tot;++i)
			if(val[i])
				val[i]=(1<<num++);
	}
	
	ll f[L][N*N][K];
	struct sol
	{
		char s[L];
		
		sol()
		{
			memset(s,0,sizeof s);
		}
		
		void print()
		{
			puts(s);
		}
		
		bool operator<(co sol&b)co
		{
			for(int i=0;i<l;++i)
				if(s[i]!=b.s[i])
					return s[i]<b.s[i];
			return 0;
		}
	}stack;
	std::vector<sol>str;
	
	void dfs(int len,int i,int state,int now)
	{
		stack.s[len-1]=now+'a';
		if(len==1)
		{
			str.push_back(stack);
			return;
		}
		for(int j=0;j<=tot;++j)
			if(f[len-1][j][state]&&ch[j][now]==i)
				dfs(len-1,j,state,meaning[j]);
		if(val[i])
			for(int j=0;j<=tot;++j)
				if(f[len-1][j][state^val[i]]&&ch[j][now]==i)
					dfs(len-1,j,state^val[i],meaning[j]);
	}
	
	void getsolution()
	{
		for(int i=1;i<=tot;++i)
			if(f[l][i][(1<<num)-1])
				dfs(l,i,(1<<num)-1,meaning[i]);
	}
	
	void solve()
	{
		f[0][0][0]=1;
		for(int i=0;i<l;++i)
			for(int j=0;j<=tot;++j)
				for(int k=0;k<(1<<num);++k)
					if(f[i][j][k])
		for(int u=0;u<26;++u)
			f[i+1][ch[j][u]][k|val[ch[j][u]]]+=f[i][j][k];
		ll ans=0;
		for(int j=0;j<=tot;++j) // edit 1:0
			ans+=f[l][j][(1<<num)-1];
		printf("%lld\n",ans);
		if(ans<=42)
		{
			getsolution();
			sort(str.begin(),str.end());
			assert(str.size()==ans);
			for(int i=0;i<ans;++i)
				str[i].print();
		}
	}
}

int main()
{
	read(l),read(n);
	for(int i=1;i<=n;++i)
	{
		scanf("%s",s[i]);
		AC::ins(s[i],strlen(s[i]));
	}
	AC::getfail();
	AC::solve();
	return 0;
}

文本生成器

给出若干个由大写字母构成的单词,问长度为 m ,由大写字母构成的字符串中,包含至少一个单词的数目.对 10007 取模.

jklover的题解

可以先求出不包含任意一个单词的字符串数目,再用总数目26m减去.

将单词建成一个 AC 自动机,类似上题,合并权值即可求出一个节点是否能被走到.

\(f[i][j]\) 表示已经走了 \(i\) 步,走到了节点 \(j\) 时的方案数. \(O(n^2)\) dp 即可.

AC自动机上面dp才是AC自动机的精髓。

co int mod=1e4+7;

int add(int x,int y)
{
	return (x+y)%mod;
}

int mul(int x,int y)
{
	return x*y%mod;
}

int qpow(int x,int k)
{
	int res=1;
	while(k)
	{
		if(k&1)
			res=mul(res,x);
		x=mul(x,x),k>>=1;
	}
	return res;
}

co int N=7777,S=26;
int n,m;
namespace AC
{
	int idx;
	int ch[N][S],fail[N],val[N];
	int f[101][N];
	
	void init()
	{
		memset(f,-1,sizeof f);
	}
	
	void ins(char*s,int len)
	{
		int u=0;
		for(int i=0;i<len;++i)
		{
			int k=s[i]-'A';
			if(!ch[u][k])
				ch[u][k]=++idx;
			u=ch[u][k];
		}
		val[u]=1;
	}
	
	void getfail()
	{
		std::queue<int>Q;
		for(int i=0;i<S;++i)
			if(ch[0][i])
				Q.push(ch[0][i]);
		while(Q.size())
		{
			int u=Q.front();Q.pop();
			for(int i=0;i<S;++i)
			{
				if(ch[u][i])
				{
					fail[ch[u][i]]=ch[fail[u]][i];
					Q.push(ch[u][i]);
				}
				else
					ch[u][i]=ch[fail[u]][i];
			}
			val[u]|=val[fail[u]];
		}
	}
	
	int dfs(int i,int j)
	{
		if(f[i][j]!=-1)
			return f[i][j];
		if(val[j])
			return 0;
		if(i==m)
			return 1;
		int&res=f[i][j]=0;
		for(int k=0;k<S;++k)
			res=add(res,dfs(i+1,ch[j][k]));
		return res;
	}
	
	void solve()
	{
		int ans=qpow(26,m);
		ans=add(ans,mod-dfs(0,0));
		printf("%d\n",ans);
	}
}
char buf[N];

int main()
{
	AC::init();
	read(n),read(m);
	for(int i=1;i<=n;++i)
	{
		scanf("%s",buf);
		AC::ins(buf,strlen(buf));
	}
	AC::getfail();
	AC::solve();
	return 0;
}

Rescue the Rabbit

现在有n个基因片段(用包含A、G、T、C的字符串表示),每个基因片段有一个权值,现在求长为L的基因的最大权值(每个基因片段重复出现算一次,不用计算多次)?

n (1 ≤ n ≤ 10),l (1 ≤ l ≤ 100)

分析

未知定长串中不同已知模板串的出现次数问题,一般做法是AC自动机上dp。

考虑背包,\(dp(i,j,k)\)表示当前串长为\(i\),在AC自动机上对应节点\(j\),已匹配的模板串的状态为\(k\)的情况是否出现。用刷表法向后转移。先枚举不定串长度,再枚举AC自动机上节点,然后枚举已知状态,最后枚举字母边转移。

时间复杂度\(O(l \cdot MaxNode \cdot 2^n \cdot SigmaSize)\)。第一维可以滚动,空间复杂度\(O(MaxNode \cdot 2^n)\)

const int MAXN=1010;
const int SigmaSize=4;

bool dp[2][MAXN][1100];
int mp[15];

struct Trie
{
	int next[MAXN][SigmaSize];
	int fail[MAXN];
	int end[MAXN];
	int root,ncnt;
	int newnode()
	{
		for(int i=0;i<SigmaSize;++i)
			next[ncnt][i]=-1;
		end[ncnt++]=0;
		return ncnt-1;
	}
	void init()
	{
		ncnt=0;
		root=newnode();
	}
	int id(char c)
	{
		if(c=='A')
			return 0;
		else if(c=='G')
			return 1;
		else if(c=='T')
			return 2;
		else
			return 3;
	}
	void insert(char*str,int v)
	{
		int now=root;
		int len=strlen(str);
		for(int i=0;i<len;++i)
		{
			int c=id(str[i]);
			if(next[now][c]==-1)
				next[now][c]=newnode();
			now=next[now][c];
		}
		end[now]|=(1<<v);
	}
	void getfail()
	{
		queue<int>Q;
		fail[root]=root;
		for(int i=0;i<SigmaSize;++i)
		{
			if(next[root][i]==-1)
				next[root][i]=root;
			else
			{
				fail[next[root][i]]=root;
				Q.push(next[root][i]);
			}
		}
		while(!Q.empty())
		{
			int now=Q.front();
			Q.pop();
			end[now]|=end[fail[now]];
			for(int i=0;i<SigmaSize;++i)
			{
				if(next[now][i]==-1)
					next[now][i]=next[fail[now]][i];
				else
				{
					fail[next[now][i]]=next[fail[now]][i];
					Q.push(next[now][i]);
				}
			}
		}
	}
	void solve(int n,int l)
	{
		memset(dp,0,sizeof(dp));
		dp[0][0][0]=1; // dp[len][node][state]
		int cur=1;
		for(int i=1;i<=l;++i)
		{
			memset(dp[cur],0,sizeof(cur));
			for(int j=0;j<ncnt;++j)
			{
				for(int k=0;k<(1<<n);++k)
				{
					for(int q=0;q<SigmaSize;++q)
					{
						int nxt=next[j][q];
						dp[cur][nxt][k|end[nxt]]=(dp[cur][nxt][k|end[nxt]]||dp[cur^1][j][k]);
					}
				}
			}
			cur^=1;
		}
		int ans=-INF;
		for(int i=0;i<ncnt;++i)
			for(int j=0;j<(1<<n);++j)
			{
				if(dp[cur^1][i][j])
				{
					int sum=0;
					for(int k=0;k<n;++k)
						if(j&(1<<k))
							sum+=mp[k];
					ans=max(ans,sum);
				}
			}
		if(ans<0)
			printf("No Rabbit after 2012!\n");
		else
			printf("%d\n",ans);
	}
}AC;

char s[110];

int main()
{
	int n,l,w;
	while(~scanf("%d %d",&n,&l))
	{
		AC.init();
		for(int i=0;i<n;++i)
		{
			scanf("%s %d",s,&w);
			AC.insert(s,i);
			mp[i]=w;
		}
		AC.getfail();
		AC.solve(n,l);
	}
    return 0;
}

posted on 2020-03-27 16:45  autoint  阅读(349)  评论(0编辑  收藏  举报

导航