洛谷P3041 思路分享(AC 自动机,dp)
https://www.luogu.com.cn/problem/P3041
题意概述
给定 \(n\) 个字符串 \(s_1,s_2,\cdots,s_n\),仅由 \(A,B,C\) 三个字符构成.
对于一个字符串 \(T\),每有任意一个 \(s_i\) 在 \(T\) 的子串中出现,\(T\) 的得分加一.
求长度恰好为 \(k\) 的字符串能获得的最大得分.
\(1\le n \le 20,1\le k\le 10^3,1\le |s_i| \le 15\).
思路
多模式串匹配,考虑 AC 自动机.
建出 AC 自动机,记录到达节点 \(u\) 的得分 \(cnt_u\),同时需要加上该字符串的所有后缀的得分.
长度恰好为 \(k\),考虑 \(dp\),第一层循环枚举长度,第二层枚举节点,这样更新是满足拓扑关系的.
时间复杂度 \(\mathcal{O}(3Lk)\),\(L\) 是 \(n\) 个字符串的总长度.
代码
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int INF = 1e9;
void solve(){
int n,K;
cin >> n >> K;
vector<string> s(n+1);
for (int i=1;i<=n;i++){
cin >> s[i];
}
vector<vector<int>> next{vector<int>(3)};
vector<int> cnt{0};
for (int i=1;i<=n;i++){
int u = 0;
for (auto& ch:s[i]){
int c = ch-'A';
if (next[u][c]==0){
next.push_back(vector<int>(3));
cnt.push_back(0);
next[u][c] = next.size()-1;
}
u = next[u][c];
}
cnt[u]++;
}
int m = next.size();
vector<int> fail(m);
queue<int> q;
for (int c=0;c<3;c++){
if (next[0][c]){
q.push(next[0][c]);
}
}
while (!q.empty()){
int u = q.front();
q.pop();
for (int c=0;c<3;c++){
if (next[u][c]){
fail[next[u][c]] = next[fail[u]][c];
cnt[next[u][c]] += cnt[fail[next[u][c]]];
q.push(next[u][c]);
}
else{
next[u][c] = next[fail[u]][c];
}
}
}
vector<int> dp(m,-INF);
dp[0] = 0;
for (int k=1;k<=K;k++){
vector<int> ndp(m,-INF);
for (int u=0;u<m;u++){
if (dp[u]==-INF) continue;
for (int c=0;c<3;c++){
int v = next[u][c];
ndp[v] = max(ndp[v],dp[u]+cnt[v]);
}
}
dp = ndp;
}
cout << *max_element(dp.begin(),dp.end()) << '\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}

浙公网安备 33010602011771号