1. 程式人生 > 其它 >[題解] P3082 [USACO13MAR]Necklace G

[題解] P3082 [USACO13MAR]Necklace G

[題解] [USACO13MAR]Necklace G

傳送門

前置知識

  • AC 自動機。

如果你不會,可以看看 這篇文章(我不會告訴你我是來推銷部落格的。

請確保您已經學會 AC 自動機。

解題報告

對於題中的兩個字串,顯然奶牛的名字是模式串,我們先把它插入到 AC 自動機中。

沒有什麼正確的貪心做法,因此考慮 DP。

設題目中項鍊的字串為 \(S\),奶牛的名字為 \(S'\)\(f[i,j]\) 表示已經考慮了 \(S\) 的前 \(i\) 個字元,在 AC 自動機上對應的第 \(j\) 個結點的最大保留長度。

  • \(nxt\) 為上一個結點 \(j\)\(i\) 指標,如果指向的不是 \(S'\)

    結束的位置,那麼 \(f[i,nxt]=max(f[i,nxt],f[i-1][j]+1)\)

  • 同樣地,如果當前位置不是 \(S'\) 的結束位置,那麼 \(f[i,j]=max(f[i,j],f[i-1][j])\)

最後答案取個 max,用總長度減去就好了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
	T x=0;char ch=getchar();bool fl=false;
	while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
	while(isdigit(ch)){
		x=(x<<3)+(x<<1)+(ch^48);ch=getchar();
	}
	return fl?-x:x;
}
#include <queue>
const int maxn = 1000 + 10;
int f[maxn*10][maxn];
namespace AC{
int t[maxn][26],fail[maxn],end[maxn],cnt=0;
void insert(char *s){
	int u=0;
	for(int i=1;s[i];i++){
		if(!t[u][s[i]-'a'])t[u][s[i]-'a']=++cnt;
		u=t[u][s[i]-'a'];
	}
	end[u]=1;
}
queue<int> q;
void build(){
	for(int i=0;i<26;i++)if(t[0][i])q.push(t[0][i]);
	while(q.size()){
		int u=q.front();q.pop();
		for(int i=0;i<26;i++){
			if(t[u][i])fail[t[u][i]]=t[fail[u]][i],q.push(t[u][i]);
			else t[u][i]=t[fail[u]][i];
		}
	}
}
}
using namespace AC;
char s[maxn],T[maxn*10];
int main(){
	cin>>T+1>>s+1;
	insert(s);build();
	int len=strlen(T+1);
	for(int i=1;i<=len;i++){
		for(int j=0;j<=cnt;j++){
			if(!end[t[j][T[i]-'a']])
				f[i][t[j][T[i]-'a']]=max(f[i][t[j][T[i]-'a']],f[i-1][j]+1);
			if(!end[j])
				f[i][j]=max(f[i][j],f[i-1][j]);
		}
	}
	int ans=0;
	for(int i=0;i<=cnt;i++)ans=max(ans,f[len][i]);
	printf("%d\n",len-ans);
	return 0;
}

小結

個人認為 AC 自動機只是簡化了 DP 的過程,並且便於理解。

本質上和其它題解的線性 DP 還是一樣的。