洛谷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;
}
posted @ 2026-06-12 13:22  kzssCCC  阅读(10)  评论(0)    收藏  举报