1. 程式人生 > 其它 >【Coel.解題報告】【生產力++!】[USACO15FEB]Censoring G

【Coel.解題報告】【生產力++!】[USACO15FEB]Censoring G

題前碎語

部落格園可以直接把網易雲音樂外鏈播放器放在邊角,這一點還是很舒服的。
邊寫程式碼邊聽\(Yunomi\)的曲子也是一種享受呢!


題目梗概:[USACO15FEB]Censoring G

題目描述

FJ 把雜誌上所有的文章摘抄了下來並把它變成了一個長度不超過 \(10^5\) 的字串 \(s\)。他有一個包含 \(n\) 個單詞的列表,列表裡的 \(n\) 個單詞記為 \(t_1 \cdots t_n\)。他希望從 \(s\) 中刪除這些單詞。

FJ 每次在 \(s\) 中找到最早出現的列表中的單詞(最早出現指該單詞的開始位置最小),然後從 \(s\) 中刪除這個單詞。他重複這個操作直到 \(s\)

中沒有列表裡的單詞為止。注意刪除一個單詞後可能會導致 \(s\) 中出現另一個列表中的單詞。

FJ 注意到列表中的單詞不會出現一個單詞是另一個單詞子串的情況,這意味著每個列表中的單詞在 \(s\) 中出現的開始位置是互不相同的。

請幫助 FJ 完成這些操作並輸出最後的 \(s\)

輸入格式

第一行是一個字串,表示文章 \(s\)

第二行有一個整數,表示單詞列表的單詞個數 \(n\)

\(3\) 到第 \((n + 2)\) 行,每行一個字串,第 \((i + 2)\) 行的字串 \(t_i\) 表示第 \(i\) 個單詞。

輸出格式

輸出一行一個字串,表示操作結束後的 \(s\)


一看就知道是要寫\(AC\)自動機的題目,所以不多說先建一棵\(Trie\)樹,然後建立失配陣列。然後對每個單詞進行匹配,匹配到就刪。
可是刪除單詞的操作怎麼辦呢?
建立一個棧,每次插入一個模式串中的字母,然後匹配。如果匹配到,就把對應的單詞彈出。
需要注意的是,這題還要再給\(AC\)自動機開一個棧表示方向,所以不能直接用\(STL\)棧,而是自己手寫一個。
程式碼如下:

#include<cstdio>
#include<cstring>
#include<queue>
#define maxn 100050
#define maxc 26
using namespace std;
int n,tot=1,trie[maxn][maxc],fail[maxn],dep[maxn];
int stack[maxn],ans[maxn],top;//stack為ac自動機的棧,ans為答案的棧
char s[maxn],t[maxn];
queue<int>Q;
void insert(char *s)
{
    int u=0,len=strlen(s);
    for(register int i=0;i<len;i++)
    {
        int c=s[i]-'a';
        if(!trie[u][c])
            trie[u][c]=++tot;
        u=trie[u][c];
    }
    dep[u]=len;//把匹配串長度存下來,方便後面計算
}
void getFail()
{
	for(register int i=0;i<maxc;i++)
		if(trie[0][i])Q.push(trie[0][i]);
	while(!Q.empty())
	{
		int u=Q.front();
		Q.pop();
		for(register int i=0;i<maxc;i++)
		{
			if(trie[u][i])
			{
				fail[trie[u][i]]=trie[fail[u]][i];
				Q.push(trie[u][i]);
			}
			else trie[u][i]=trie[fail[u]][i];
		}
	}
}
inline void query(char *s)
{
	int len=strlen(s),u=0;
	for(register int i=0;i<len;i++)
	{
		int c=s[i]-'a';
		u=trie[u][c];
		ans[++top]=i;
		stack[top]=u;
		if(dep[u])
		{
			top-=dep[u];//前面把dep算出來就是為了這裡更方便
			/*當然也可以把每個字串存下來,在這裡呼叫strlen計算*/
			u=top<=0?0:stack[top];//小心減過頭
		}
	}
}
int main()
{
    scanf("%s%d",s,&n);
    for(register int i=1;i<=n;i++)
    {
        scanf("%s",t);
        insert(t);
    }
    getFail();
    query(s);
    for(register int i=1;i<=top;i++)
    {
    	printf("%c",s[ans[i]]);
	}
    return 0;
}

題後閒話

發現抄題目梗概的時候能直接檢視原始碼複製,方便許多。
真是太好了\(qwq\)