传送门:点击打开链接
题意:一个长为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; }