题目链接
题目大意:
可以选择一个子串(也可以不选)进行反转,求出操作后的字符串中的最长子串,其中这个子串中要求所有字符都没有重复。
解题思路:
首先可以将题目简化为选择两个子串,然后将他们连接起来且字符不重复,然后求出这两个子串的和最长是多少,因为两个子串中字符不重复就保证位置不会相交,所以不用再考虑位置的问题了。
所以设置状态dp[sta]:表示当前这个状态和他的所有子集中的最长的不重复子串的长度是多少。
例如:sta=1010:表示{bd}和他的子集∅,{b},{d}的最长的子串的长度是多少。
首先对每个位置暴力求出从当前位置开始的不重复的子串,然后将状态保存起来,dp[sta]=len。最大计算次数就是1e6*20。
for (int i=1;i<=n;i++)
{
int sum=0;
for (int j=i;j<=n;j++)
{
if(sum&(1<<(s[j]-'a')))
break;
sum|=(1<<(s[j]-'a'));
dp[sum]=j-i+1;
}
}
然后进行递推,就是要取子集的答案和子集中的答案的最大值。计算次数也大概是1e6*20。
for(int i=0;i<(1<<20);i++)
{
for (int j=0;j<20;j++)
{
if (i&(1<<j))
dp[i]=max(dp[i],dp[i^(1<<j)]);
}
}
现在两个不重复子串就可以看成是两个不相交集合,则枚举其中一个集合sta,直接查询另一个不相交的集合(((1<<20)-1)^sta),然后去max就可以了。
#include<bits/stdc++.h>
using namespace std;
mt19937 rng_32(chrono::steady_clock::now().time_since_epoch().count());
typedef long long ll;
const int maxn=1e6+10;
int dp[1<<20];
char s[maxn];
int main()
{
cin>>s+1;
int n=strlen(s+1);
for (int i=1;i<=n;i++)
{
int sum=0;
for (int j=i;j<=n;j++)
{
if(sum&(1<<(s[j]-'a')))
break;
sum|=(1<<(s[j]-'a'));
dp[sum]=j-i+1;
}
}
for(int i=0;i<(1<<20);i++)
{
for (int j=0;j<20;j++)
{
if (i&(1<<j))
dp[i]=max(dp[i],dp[i^(1<<j)]);
}
}
int ans=0;
int sum=(1<<20)-1;
for (int i=0;i<(1<<20);i++)
{
ans=max(ans,dp[i]+dp[sum^i]);
}
cout<<ans;
return 0;
}