要想上2000分,先刷几百道2000+的题再说 ———某神
题目 | E | F |
---|---|---|
赛时是否尝试 | × | × |
tag | math | bitmask |
难度 | 2000 | 2400 |
状态 | ∅ | √ |
解
E
待定
F
传送门
第一次接触状态压缩dp的题。这道题转换问题的思路非常巧妙。
原问题:
已知: 一个字符串,可进行不超过一次操作
操作限定: 选择某个子串,使其在原串中翻转
目的:使原串的特征值最大
串的特征值:串任意没有重复字符的子串,其包含字符的种类数
问题的转换:
首先选定一个子串a,之后再找到另一个子串b,使得a与b没有公共的字符,枚举a与b,就能求出最大的特征值(如果a与b相邻,则翻转b即可,反之,将a与b之间的区间与b连接在一起进行翻转,即可将b翻转到a的相邻位置)
因为合法子串的长度不会超过20位(题目限定),所以每个合法子串我们都可以将其用一个二进制长度为20的整型表示
与a 01互补的“串”不一定出现在原串中,所以对于每一个串,我们都要求出其最大的合法长度,以便我们最后计算答案
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i = (a);i>=(b);--i)
#define fo(i,a,b) for(int i =(a);i<(b);++i)
#define de(x) cout<<#x<<" = "<<x<<endl;
#define endl '\n'
#define mem(a,b) memset(a,b,sizeof(a));
#define ls(p) ((p)<<1)
#define rs(p) (((p)<<1)|1)
using namespace std;
typedef long long ll;
const int mn = 105;
int main(){
string s;
cin >> s;
vector <int> dp(1<<20);
for(int i = 0; i < int(s.size()); ++i){// start char of substring
vector<bool> used(20);
int mask = 0;
for(int j = 0; i + j < int(s.size()); ++j){
if(used[s[i + j]-'a']) break; // current char has appear in the substring
used[s[i+j]-'a'] = true; // update mask
mask |= 1<<(s[i+j]-'a'); // update dp hashtable
dp[mask] = __builtin_popcount(mask);
}
}
for(int mask = 0;mask < (1<<20);++mask){
for (int pos = 0; pos < 20;++pos) {
if((mask >> pos) & 1) {
dp[mask] = max(dp[mask],dp[mask ^ (1 << pos)]);
//because mask ^ (1<<pos) < mask, dp[mask ^ (1 << pos)] have been iterated
}
}
}
int ans = 0;
for(int mask = 0;mask < (1<<20);++mask) {
if(dp[mask] == __builtin_popcount(mask)){
/*
one interger's mask is absolutely less than or equal to itself
if mask unequals to itself ,that mean this substring do not appear
but it can show the complement string's max number
*/
int comp = ~mask & ((1 << 20)-1);
// get complement string's max number
ans = max(ans,dp[mask] + dp[comp]);
}
}
cout<<ans<<endl;
}