动态规划——不重复子序列DP法
考虑一个基本模型:
给定一个字符串 S S S,请问 S S S中有多少个不重复的子序列。这里的子序列指,删掉 S S S中某些字符,剩下的字符按照原有的顺序拼成的字符串。
容易想到一个基本的DP解法:
设 d p i dp_i dpi为以 S i S_i Si结尾(必选)的 S [ 1 , i ] S[1,i] S[1,i]中的子序列的数量。特别的,设 d p 0 = 0 dp_0=0 dp0=0,对应一个空字符串。那么有如下转移方程:
d p i = ∑ j = 0 i − 1 d p j dp_i = \sum_{j = 0}^{i-1}dp_j dpi=j=0∑i−1dpj
但是我们会重复进行记录相同的子序列,因此我们改变一下DP方程:
d p i = ∑ j = k i − 1 d p j dp_i = \sum_{j = k}^{i-1}dp_j dpi=j=k∑i−1dpj
则此问题的答案为 ∑ i = 1 n d p i \sum_{i=1}^n dp_i ∑i=1ndpi。
其中 k k k是满足 S k = S i S_k = S_i Sk=Si中最大的 k k k,为什么这样可行呢?如果我们对小于 k k k的 d p j dp_j dpj进行计数的话,则这个子序列已经被 d p k dp_k dpk所计数过了,因此加到上述和中必然会重复计数,加到 S k S_k Sk后面是可行的,因为我们对原有的子序列的末尾长度增加了一。
模板题:
class Solution
{
public:
int distinctSubseqII(string s)
{
const int mod = 1e9 + 7;
s = "$" + s;
vector<int> dp(s.size());
vector<int> prv(26);
int ans = 0;
for (int i = 1; i < s.size(); i++)
{
if (prv[s[i] - 'a'] == 0)
dp[i] = (dp[i - 1] + 1) % mod; // 没有前驱位置,加上1,代表单独出现
else
dp[i] = (dp[i - 1] - dp[prv[s[i] - 'a'] - 1]) % mod; // 不重复子序列DP
ans = (ans + dp[i]) % mod; // 更新答案
dp[i] = (dp[i] + dp[i - 1]) % mod; // 维护前缀和
prv[s[i] - 'a'] = i; // 维护最后出现的位置
}
return (ans % mod + mod) % mod;
}
};
例题
#include <bits/stdc++.h>
#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)
using namespace std;
int pos[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
typedef long long ll;
const ll mod = 1e9 + 7;
ll dp[200005];
int las[26];
int main()
{
string str;
cin >> str;
dp[0] = 1;
for (int i = 1; i <= str.size(); i++)
{
int curr = str[i - 1] - 'a';
ll sum = 0;
for (int j = max(las[curr] - 1, 1); j < i - 1; j++)
{
sum = (sum + dp[j]) % mod;
}
if (las[curr] == 0)
{
sum = (sum + 1) % mod;
}
las[curr] = i;
dp[i] = sum;
}
ll ans = 0;
for (int i = 1; i <= str.size(); i++)
{
ans = (ans + dp[i]) % mod;
}
cout << ans;
return 0;
}
此题要求的是可以重复,如果要求不重复呢?
使用前缀和可以优化至 O ( n ) O(n) O(n)。
#include <bits/stdc++.h>
#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)
using namespace std;
int pos[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
typedef long long ll;
const ll mod = 1e9 + 7;
ll dp[100005];
int main()
{
string str;
cin >> str;
dp[0] = 1;
for (int i = 1; i <= str.size(); i++)
{
if (str[i - 1] == '0')
{
for (int j = i - 1; j >= 0; j--)
{
if (j == 0 || str[j - 1] == str[i - 1])
{
dp[i] += dp[j];
break;
}
}
}
else if (str[i - 1] == '1')
{
for (int j = i - 1; j > 0; j--)
{
if (str[j - 1] == '0')
{
dp[i] += dp[j];
}
if (str[j - 1] == '1')
{
dp[i] += dp[j];
break;
}
}
}
else if (str[i - 1] == '2')
{
for (int j = i - 1; j > 0; j--)
{
if (str[j - 1] == '1')
{
dp[i] += dp[j];
}
if (str[j - 1] == '2')
{
dp[i] += dp[j];
break;
}
}
}
}
ll ans = 0;
for (int i = 1; i <= str.size(); i++)
{
if (str[i - 1] == '2')
ans += dp[i];
}
cout << ans;
return 0;
}
纯不重复子序列DP法,"0"单独考虑即可。