【题解】Codeforces533F. Encoding 按字符分解,AC自动机

给一个文本串,一个模式串,2e5,输出文本串的哪些位置和模式串荣誉匹配。

A和B荣誉匹配是指,A和B长度相同,并且如果存在若干个没有重复字母的荣誉转换<c1,c2>,把A中的每个c1都换成c2,每个c2换成c1,然后它和B相等。


把文本串和模式串分别按字符分解成 26 26 个字符串,所谓按字符分解是指:

  1. 第一个串,在原串是 a a 的位置上是1,否则是0
  2. 第二个串,在原串是 b b 的位置上是1,否则是0
  3. 以此类推。

26 26 个模式串建立AC自动机,把26个文本串同时放在上面跑,每个时刻,可以得到哪些文本串与哪些模式串相匹配。

记录所有使分解后的 T T 不为全0的字符,称为非空分解串。对于一个非空的 T T 分解串,要么没有串能和匹配,要么有唯一一个非空的 S S 分解串和它匹配。

对于所有非空分解串 T a T_a ,必须满足:

  1. 有一个 S b S_b 与其匹配。
  2. b = = a b==a 或者 T b T_b S a S_a 匹配。
  3. 否则本位置荣誉匹配失败。

想了不少时间空串怎么处理,最后发现不要处理就行了。把所有非空串处理掉,空串自然就没了。

以前还没有这样按字符分解然后插入,感觉很巧妙。

/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 5200016, MOD = 1000000007;


//Trie树(图)/是否为终结点/失配链接/后缀链接,都是节点的属性
int sz = 0;
int ch[M][2], ed[M], fail[M], last[M];

// 向Trie树中尝试插入一个模式串, 返回插入后的节点编号
void insert(const char *s, int id)
{
    int u = 0;
    for(int i = 0; s[i]; i++)
    {
        int &v = ch[u][s[i] - '0'];
        if(!v) v=++sz;
        u = v;
    }
    ed[u] = id;
}

// 构建AC自动机
void build()
{
    queue<int> q;
    for(int v:ch[0]) if(v)
        fail[v] = 0, q.push(v);
    while(!q.empty())
    {
        int u = q.front(); q.pop();
        for(int i = 0; i < 2; i++)
        {
            int &v = ch[u][i];
            if(v) 
            {
                q.push(v);
                fail[v] = ch[fail[u]][i];
                last[v] = ed[fail[v]] ? fail[v] : last[fail[v]]; //后缀链接
            }
            else v = ch[fail[u]][i]; //建立trie图
        }
    }
}

char tex[200016], pat[200016], tpat[200016];
int has[M]; //每个字母是否有,字母编号是1到26

int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

	int n = read(), m = read();
	scanf("%s %s", tex, pat);
	for(int c=1; c<=26; ++c)
	{
		for(int i=0; i<m; ++i)
		{
			if(pat[i]==c-1+'a')
			{
				tpat[i] = '1';
				has[c] = 1;
			}
			else 
				tpat[i] = '0';
		}
		if(has[c]) insert(tpat, c); //只插入非空串
	}
	build();
	vector<int> ans;
	int now[30] = {}, mat[30] = {};
	for(int i=0; i<n; ++i)
	{
		memset(mat, 0, sizeof(mat));
		for(int c=1; c<=26; ++c)
		{
			int trs = (tex[i]==c-1+'a') ? 1 : 0;
			now[c] = ch[now[c]][trs];

			int match = ed[now[c]];
			if(match) //匹配了一个非空串
				mat[match] = c;
		}

		int suc = 1;
		for(int c=1; c<=26; ++c) if(has[c])
		{
			//printf("%c %c\n",c+'a'-1,mat[c]+'a'-1 );
			if(!mat[c]) 
			{
				suc = 0;
				break;
			}
			int rc = mat[c];
			if(mat[rc]==c || !has[rc]) //正好匹配(包括相等)或者空
				continue;
			else
			{
				suc = 0;
				break;
			}	
		}
		//printf("i=%d: ",i );
		if(suc)
		{
			//printf("suc %d",i-m+2);
			ans.push_back(i-m+2);
		}
		//printf("\n");
	}
	printf("%d\n",ans.size() );
	for(auto x:ans)
		printf("%d ",x );

    return 0;
}

inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
发布了375 篇原创文章 · 获赞 305 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/m0_37809890/article/details/103193420