题目链接
这题真没想到状压dp可以写,看了别人代码才看会的。
题意:给你一个只包含前20个英文字母的串。你可以执行一次操作,将这个串的任意子串翻转一次。让你求一个最长的没有重复字母的字串。
题解:首先看到只有二十个英文字母,无脑上状压dp。可以将这个串的任意子串翻转一次,实际上就是可以让两个去重的子字符串合并在一起。那么这个题就变成了枚举子集,表示我们必须选取这一段,然后为了使答案最优,所以去找他的补集的最大子集。那么我们是不是可以先预处理出这个串所有的无重复字符的子串,并用二进制表示其状态。再dp求出每个补集的最大子集,再o(n)求最大答案就行了。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
char s[N];
int vis[21], sta[1 << 20], mx[N];
int main()
{
scanf("%s", s);
int n = strlen(s);
for(int i = 0; i < n; ++i){//预处理出所以无重复字母子串
memset(vis, 0 ,sizeof vis);
int cnt = 0, tmp = 0;
for(int j = i; j < n; ++j){//最多二十位
if(vis[s[j] - 'a']) break;
else {
tmp |= (1 << (s[j] - 'a'));
vis[s[j] - 'a'] = 1;
sta[tmp] = ++cnt;
}
}
}
int m = (1 << 20) - 1;
for(int i = 0; i <= m; ++i)
for(int j = 0; j < 20; ++j)
if(i & (1 <<j))
sta[i] = max(sta[i], sta[i ^ (1 << j)]);//枚举这个集合的最大子集
int ans = 0;
for(int i = 1; i <= m; ++i){
int tmp = m ^ i;
ans = max(ans, sta[i] + sta[tmp]);//子集 + 补集
}
printf("%d\n", ans);
}