状压dp fzu2218 Simple String Problem

传送门:点击打开链接

题意:一个长为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;
}


你可能感兴趣的:(状压dp fzu2218 Simple String Problem)