传送门:点击打开链接
题意:一个长为n(n<=2000)的字符串,由前k(k<=16)个小写字母组成
求两段子串A和B,A和B中间没有共用的字母类型,求len(A)*len(B)的最大值
思路:当时想到了3种方法,感觉各有优势,都来BB一下。
第一种方法:复杂度O(2^k*n)
用s枚举每种字符在A串中是否使用,然后原串就能变成01串了,那么1表示在A中可以使用,0表示在B中可以使用
就变成了求0的连续最长长度,和1的连续最长长度,然后两个相乘,就得到对于一个s的答案了。
第二种方法:复杂度O(k*n^2)
用end[i]表示字母i最后一次出现的位置。然后开始枚举A串的右端点位置,然后它左端点应该取哪些位置呢?一定是某个字母最后一次出现的位置的右边那个。
然后和方法一一样,得到原串的01串,然后再求出B的最长长度。
第三种方法:复杂度O(n^2+k*2^k)
首先,n^2枚举所有区间,然后得到每个区间里面的字母的使用情况。
设dp[s],s表示为第i个字母是否使用的状压,dp[s]表示一个子串恰好把s里为1的字母全部使用上的时候,得到的最长长度
那么我们会想到,如果A串为s,那么B串就是s的反面的子集,但是枚举子集会变得非常慢,我们中间可以来一次预处理。
本来dp[s]表示一个子串恰好把s里为1的字母全部使用上的时候,得到的最长长度,我可以通过一次状压dp,把s子集的状态转移到s上
可以把dp[s]更新成一个子串只使用了s里为1的字母的子集的时候,得到的最大长度,这一步用状压dp来搞
最后再枚举s,得到其反面,两者相乘就行了
#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<cstdio>
#include<cctype>
#include<string>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
#define fuck(x) cout<<"["<<x<<"]"
#define FIN freopen("input.txt","r",stdin)
#define FOUT freopen("output.txt","w+",stdout)
using namespace std;
typedef long long LL;
typedef pair<int, int>PII;
const int MX = 2e3 + 5;
const int INF = 0x3f3f3f3f;
int n, T, k;
int d[1 << 18];
char a[MX];
int main() {
//FIN;
scanf("%d", &T);
while(T--) {
scanf("%d%d%s", &n, &k, a);
memset(d, 0, sizeof(d));
for(int i = 0; i < n; i++) {
int S = 0;
for(int j = i; j < n; j++) {
S |= 1 << (a[j] - 'a');
d[S] = max(d[S], j - i + 1);
}
}
for(int S = 0; S < 1 << k; S++) {
for(int i = 0; i < k; i++) if(S >> i & 1) {
d[S] = max(d[S], d[S ^ (1 << i)]);
}
}
int ans = 0;
for(int S = 0; S < 1 << k; S++) {
ans = max(ans, d[S] * d[(1 << k) - 1 ^ S]);
}
printf("%d\n", ans);
}
return 0;
}