题解-gym102759G[*medium]

题面

gym102759G LCS 8

给定一字符串 (S)。求有多少不同的长度为 (T),使得 (|S| = |T|),且 (S)(T) 的最长公共子序列长度大于等于 (n-k)。所有字符都为大写字母。

数据范围:(1 le |S| le 5 imes 10^4, 0 le k le 3)

题解

(下文令 (n = |S|)

我们是如何 dp(lcs) 的?

记录 (a_{i, j}) 表示 (S) 的前 (i) 个字符 和 (T) 的前 (j) 个字符的 (lcs) 。有 (a_{i, j} = max(a_{i - 1, j}, a_{i, j - 1}, a_{i - 1, j - 1} + [a_i = b_j]))

如果我们对于每一个 (T) 中的位置 (j),记录对于所有 (i)(lcs(i, j)) 是多少。但是这样状态数过多,对于每一个位置 (j) 都有 (n^n) 个。

用一个常见的差分套路,记录 (lcs(i, j) - lcs(i - 1, j)),这个值只有 (0)(1),这样状态数减少到 (2^n)

我们发现 (lcs(i, j) le min(i, j)),而由这个点对 ((i, j)) 更新的 (lcs(S, T)) 不超过 (lcs(i, j) + min(n - i, n - j))

如果 (min(i, j) + min(n - i, n - j) ge n - k)((i, j)) 这个点对才可能对答案有贡献。化简一下得到 (max(i, j) - min(i, j) le k) 。所以我们记录满足 (|i - j| le k) 的状态即可。

再记录一下 (lcs(i, i)),状态数变成了 ((k + 1) 2^{2k})

转移很简单,枚举 (T_j) 是哪一个字符,然后更新最长公共子序列的状态就可以了。

时间复杂度垃圾,是 (Theta(26nk^22^{2k}))。空间复杂度 (Theta(nk 2^{2k})) ,可以滚动数组优化到 (Theta(k 2^{2k}))

被卡常了,加了个类似记忆化的东西就过了。

CF578D 是这题的弱化版,把 (m) 改为 (1),字符集改一改就过了。

代码

gym102759G:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 7;
const int mod = 1e9 + 7;
int n, m, now[N], nxt[N], ans;
int dp[N][1 << 7][5]; 
bool mp[256];
char s[N]; 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> (s + 1) >> m, n = strlen(s + 1);
	if(m == 0) return cout << "1
", 0;
	dp[0][0][0] = 1;
	for(int i = 1; i <= n; i++) {
		memset(mp, 0, sizeof(mp));
		for(int j = max(i - m, 1); j <= min(i + m, n); j++) mp[s[j]] = 1;
		
		for(int j = 0; j < (1 << (m << 1)); j++) {
			for(int k = 0; k <= m; k++) if(dp[i - 1][j][k]) { 
				now[i - 1] = i - 1 - k;
				for(int t = 0; t < m; t++) now[i + t] = now[i + t - 1] + (j >> (t + m) & 1);
				for(int t = 1; t <= m; t++) if(i - 1 - t > 0) now[i - 1 - t] = now[i - t] - (j >> (m - t) & 1);
				int to1 = -1, to2;
				for(char t_i = 'A'; t_i <= 'Z'; t_i++) {
					if(!mp[t_i] && ~to1) {
						(dp[i][to1][to2] += dp[i - 1][j][k]) %= mod;
						continue;
					}
					int msk = 0;
					for(int wz = max(i - m, 1); wz <= i + m; wz++) 
						nxt[wz] = max(max(now[wz], now[wz - 1] + (s[wz] == t_i)), nxt[wz - 1]);
					for(int wz = max(i - m, 0); wz <= i + m - 1; wz++) if(nxt[wz] ^ nxt[wz + 1]) msk |= (1 << (wz - i + m));
					(dp[i][msk][i - nxt[i]] += dp[i - 1][j][k]) %= mod;
					if(!mp[t_i]) to1 = msk, to2 = i - nxt[i];
				}
			}
		}
	}
	for(int i = 0; i < (1 << (m << 1)); i++) for(int j = 0; j <= m; j++) (ans += dp[n][i][j]) %= mod;
	cout << ans << "
";
	return 0;
} 

CF578D:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 7;
int n, S, now[N], nxt[N];
ll dp[N][4][3], ans; 
bool mp[26];
char s[N]; 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	cin >> n >> S, cin >> (s + 1);
	dp[0][0][0] = 1;
	for(int i = 1; i <= n; i++) {
		memset(mp, 0, sizeof(mp));
		for(int j = max(i - 1, 1); j <= min(i + 1, n); j++) mp[s[j] - 'a'] = 1;
		for(int j = 0; j < 4; j++) {
			for(int k = 0; k <= 1; k++) if(dp[i - 1][j][k]) { 
				now[i - 1] = i - 1 - k, now[i] = now[i - 1] + (j >> 1 & 1);
				if(i - 2 > 0) now[i - 2] = now[i - 1] - (j & 1); 
				int to1 = -1, to2;
				for(char t_i = 'a'; t_i <= 'a' + S - 1; t_i++) {
					if(!mp[t_i - 'a'] && ~to1) {
						dp[i][to1][to2] += dp[i - 1][j][k];
						continue;
					}
					int msk = 0;
					for(int wz = max(i - 1, 1); wz <= i + 1; wz++) nxt[wz] = max(max(now[wz], now[wz - 1] + (s[wz] == t_i)), nxt[wz - 1]);
					for(int wz = max(i - 1, 0); wz <= i; wz++) if(nxt[wz] ^ nxt[wz + 1]) msk |= (1 << (wz - i + 1));
					dp[i][msk][i - nxt[i]] += dp[i - 1][j][k];
					if(!mp[t_i - 'a']) to1 = msk, to2 = i - nxt[i];
				}
			}
		}
	}
	for(int i = 0; i < 4; i++) ans += dp[n][i][1];
	cout << ans << "
";
	return 0;
} 

祝大家学习愉快!

原文地址:https://www.cnblogs.com/zkyJuruo/p/14401116.html