【Codeforces】CF 2005 C

发布于:2024-10-10 ⋅ 阅读:(10) ⋅ 点赞:(0)

Lazy Narek

#动态规划 #字符串 #贪心

题目描述

Narek is too lazy to create the third problem of this contest. His friend Artur suggests that he should use ChatGPT. ChatGPT creates n n n problems, each consisting of m m m letters, so Narek has n n n strings. To make the problem harder, he combines the problems by selecting some of the n n n strings possibly none and concatenating them without altering their order. His chance of solving the problem is defined as s c o r e n − s c o r e c score_n - score_c scorenscorec, where s c o r e n score_n scoren is Narek’s score and s c o r e c score_c scorec is ChatGPT’s score.

Narek calculates s c o r e n score_n scoren by examining the selected string (he moves from left to right). He initially searches for the letter "n" \texttt{"n"} "n", followed by "a" \texttt{"a"} "a", "r" \texttt{"r"} "r", "e" \texttt{"e"} "e", and "k" \texttt{"k"} "k". Upon finding all occurrences of these letters, he increments s c o r e n score_n scoren by 5 5 5 and resumes searching for "n" \texttt{"n"} "n" again (he doesn’t go back, and he just continues from where he left off).

After Narek finishes, ChatGPT scans through the array and increments s c o r e c score_c scorec by 1 1 1 for each letter "n" \texttt{"n"} "n", "a" \texttt{"a"} "a", "r" \texttt{"r"} "r", "e" \texttt{"e"} "e", or "k" \texttt{"k"} "k" that Narek fails to utilize (note that if Narek fails to complete the last occurrence by finding all of the 5 5 5 letters, then all of the letters he used are counted in ChatGPT’s score s c o r e c score_c scorec, and Narek doesn’t get any points if he doesn’t finish finding all the 5 letters).

Narek aims to maximize the value of s c o r e n − s c o r e c score_n - score_c scorenscorec by selecting the most optimal subset of the initial strings.

输入格式

In the first line of the input, you’re given a single integer t t t ( 1 ≤ t ≤ 1 0 5 1 \le t \le 10^5 1t105), the number of test cases. Then the description of each test case follows.

In the first line of each test case, you’re given two integers n , m n, m n,m ( 1 ≤ n , m ≤ 1 0 3 1 \le n, m \le 10^3 1n,m103), the number of strings and the length of each string.

In the next n n n lines, you’re given n n n strings, each having a length of m m m. The strings only contain lowercase letters of the English alphabet.

The sum of values of n ⋅ m n \cdot m nm over all test cases does not exceed 1 0 6 10^6 106.

输出格式

For each test case, output a single integer: the maximal possible value of s c o r e n − s c o r e c score_n - score_c scorenscorec.

样例 #1

样例输入 #1

4
5 2
nn
aa
rr
ee
kk
1 5
narek
1 4
nare
5 7
nrrarek
nrnekan
uuuuuuu
ppppppp
nkarekz

样例输出 #1

0
5
0
7

解法

解题思路

首先每个字符串都有选择和不选择的两种方式,但是,如果选择了这个字符串,那么一定会选择它的所有匹配的子串。 因此,对于前者,我们使用动态规划,而对于动态规划中的子问题,则是直接使用贪心来匹配子串。

因为每次匹配,最后可能会以 [ n , a , r , e , k ] [n,a,r,e,k] [n,a,r,e,k]中其中一个结尾,因此设计动态规划状态为 f i , j f_{i,j} fi,j,表示选择第 i i i个字符串的时候,以 n a r e k narek narek的第 j j j个字符结束。

注意这里的 j = { 0 , 1 , 2 , 3 , 4 } j = \{0,1,2,3,4\} j={0,1,2,3,4}分别对应以 { k , n , a , r , e } \{k,n,a,r,e\} {k,n,a,r,e}结尾。

那么显然有以下的转移:
{ f i , j = max ⁡ ( f i , j , f i − 1 , j )    j ∈ { 0 , 1 , 2 , 3 , 4 } f i , k = max ⁡ ( f i , k , f i − 1 , j   + s u m j k )    j , k ∈ { 0 , 1 , 2 , 3 , 4 } \begin{cases} f_{i,j} = \max(f_{i,j},f_{i-1,j} ) \ \ j \in\{0,1,2,3,4\} \\ f_{i,k} = \max(f_{i,k},f_{i-1,j} \ +sum_j^k ) \ \ j,k \in \{0,1,2,3,4\} \end{cases} {fi,j=max(fi,j,fi1,j)  j{0,1,2,3,4}fi,k=max(fi,k,fi1,j +sumjk)  j,k{0,1,2,3,4}

其中 s u m j k sum_j^k sumjk表示贪心地从 j j j结尾的字符开始选,选到 k k k结尾的字符获得的贡献。

最后,我们计算答案的时候,要减去不完整的部分的贡献,比如我们选择答案以 j = 3 j=3 j=3,即 r r r结尾,那么答案贡献需要减去 3 3 3,因为要丢掉 r e k rek rek这三个贡献,并且加到 G P T GPT GPT上。

所以答案为 max ⁡ j = 0 j ≤ 4 ( f n , j − j ) \max_{j=0}^{j \leq4}(f_{n,j} - j) maxj=0j4(fn,jj)

代码


const int N = 1e3 + 10;
 
int f[N][5];
std::string t = "narek";
void solve() {
	int n, m;
	std::cin >> n >> m;
 
	std::vector<std::string>a(n + 1);
	for (int i = 1; i <= n; ++i) {
		std::cin >> a[i];
	}
 
	memset(f, -0x3f, sizeof(f));
 
	f[0][0] = 0;
	for (int i = 1; i <= n; ++i) {
		for (int j = 0; j < 5; ++j) {
			int k = j, res = f[i - 1][j];
			for (auto& c : a[i]) {
				if (c == t[k]) {
					++k;
					if (k == 5) {
						res += 5;
						k = 0;
					}
				}
				else if (t.find(c) != std::string::npos) {
					--res;
				}
			}
			f[i][k] = std::max(f[i][k], res);
		}
		for (int j = 0; j < 5; ++j) {
			f[i][j] = std::max(f[i][j], f[i - 1][j]);
		}
	}
 
 
	int res = 0;
	for (int i = 0; i < 5; i++) {
		res = std::max(res, f[n][i] - i);
	}
	std::cout << res << "\n";
 
}
 
signed main() {
	std::ios::sync_with_stdio(0);
	std::cin.tie(0);
	std::cout.tie(0);
 
	int t = 1;
	std::cin >> t;
 
	while (t--) {
		solve();
	}
}