传送门:1234F
标签:动态规划
题目大意
你将得到一个只包含前 20 个小写字母(‘a’,‘b’,…,‘t’)的字符串 s。字符串 s 的子串 s[l;r] 是字符串 slsl+1…sr。你可以执行以下操作 不多于一次:选择某个子串 s[l;r] 并反转它(即字符串 slsl+1…sr 变为 sr,sr-1…sl)。你的目标是最大化 s 中由唯一字符组成的最长子串的长度。
输入:输入的第一行包含一个字符串 s,其长度不超过106,且仅包含 ‘a’, ‘b’, … , ‘t’(前 20 个小写字母)。
输出:输出一个整数——在反转不多于一次的情况下,s 中由唯一字符组成的最长子串的最大可能长度。
算法分析
-
我们可以将问题简化为以下内容:找到两个字母不相交的给定字符串的子串,且它们的总长度是最大的。为什么我们可以进行这样的简化?这是因为我们的答案最多由两个不相交的部分组成:一个固定的子串和附加到第一个子串上的至多一个子串。我们可以通过一次反转操作将任何其他子串附加到第一个子串上。
-
然后遍历所有包含不同字母的子串,长度不超过 AL(AL 是字母表的大小)。我们这样做的复杂度为O(nAL)。让当前包含不同字母的子串为 s[i;j]。我们创建与该子串对应的掩码:第 pos 位为 1 当且仅当 pos 字母出现在子串中,否则为 0(字母从 0 开始计数)。
-
接下来要存储所有掩码,因为当前问题可以简化为以下内容:我们有一组掩码,需要找到一对掩码,它们不相交且它们的总一位数是可能的最大值,不过这种减少不如以前明显。
-
那么如何解决这个问题呢?我们可以使用动态规划。dp_mask 是给定字符串中存在的掩码的一位数的最大值,并且它是 mask 的子掩码。如何计算此动态规划?首先,字符串中所有掩码的所有 dp_mask 值等于相应掩码的一位数。让我们遍历从 0 到 2AL-1 的所有掩码。让当前掩码为 mask。然后尝试用 mask 的一个子掩码更新答案。显然,由于动态规划,我们需要从掩码中删除最多一个位以覆盖所有可能更新答案的子掩码。因此,让我们遍历 mask 中的所有位,让当前位为 pos。如果该位为零,则跳过它。否则,更新 dp_mask := max(dp_mask, dp_mask(2pos)),其中 ^ 表示异或运算。计算完此动态规划后,我们可以最终计算出答案。让我们遍历字符串中出现的所有掩码,让当前掩码为 mask。我们可以用 mask 以及 dp_mask(2AL-1)(mask(2AL-1) 是 mask 的完成)的一位数更新答案。
代码实现
#include<bits/stdc++.h>
using namespace std;
const int N = 1 << 20;
int main(){
ios_base::sync_with_stdio(0);
cin.tie(0);
string s;
cin >> s;
vector<int> d(N), p(N);
for(int i = 0;i < s.size();i++){
for(int j = i, k = 0;j < s.size();j++){
int l = 1 << (s[j] - 'a');
if(k & l) break;
k |= l;
p[k] = true;
d[k] = __builtin_popcount(k);
}
}
for(int i = 1;i < N;i++)
for(int j = 0;j < 20;j++)
if((1 << j) & i)
d[i] = max(d[i ^ (1 << j)], d[i]);
int res = 0;
for(int i = 0;i < N;i++)
if(p[i]) res = max(res, __builtin_popcount(i) + d[(N - 1) ^ i]);
cout << res << '\n';
}