FZU 2218 Simple String Problem(状态压缩DP)
tags : acm
组队赛时遇到的一道题,最后几分钟才做出来。说实话我做的时候也完全没有底,没想到竟然能一发AC
题意
给定一个长度为n,由字母表前k个字母组成的字符串。取其中的两个子串S2,S2,使得这两个字符串中没有相同的字符,求strlen(S1)*strlen(S2)的最大值。
解析
每个子串都可以得到一个01组成的”状态”,其中1表示子串中存在对应的字母,而0表示不存在。由于k最大为16,所以可以用16位二进制表示所有的状态,这样状态压缩就完成了。
dp[X]为状态X及其”子集“对应的字符串的最大长度。此处的”子集“表示状态
X′
中1的位数比
X
少,且满足
则所求的答案可以以max(dp[ X ]*dp[ (1 << k) - X]
来表示。
总的方向有了,要我们要做到就是求出所有状态X对应的dp[X]。
首先,通过一次基础的dp,我们可以将dp[]数组的值设置为每个状态下子串的最大长度。状态转移方程式见代码。
然后就是标准的状压DP做法,从小到大处理每个状态
X
,我们找到所有前驱状态dp[X]=max(dp[X],dp[X'])
更新dp[X]的值,从而得到前面所说的dp[]数组,也就能得到所求的最大值了。
代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
using namespace std;
#define ll __int64
const int maxn = 3000;
const int INF = 1 << 30;
char str[maxn];
int num[maxn];
int dp[66666];
int main()
{
int T, n, k;
scanf("%d",&T);
while (T--)
{
memset(dp, 0, sizeof(dp));
scanf("%d%d",&n,&k);
getchar();
scanf("%s",str);
int maxnx=0;
for (int i = 0; i < n; i++)
{
int num = 0;
for (int j = i; j < n; j++)
{
int x = 1 << (str[j] - 'a');
num |= x;
dp[num] = max(dp[num],j-i+1);
}
}
for (int i = 0; i < (1<<k); i++)
{
for (int j = 0; j < 16; j++)
{
int x = 1 << j ;
if (x&i)
dp[i] = max(dp[i], dp[i - x]);
}
}
int ans = 0;
for (int i = 1; i < (1<<k); i++)
{
ans = max(ans, dp[i] * dp[(1<<k)-1 - i]);
}
printf("%d\n",ans);
}
return 0;
}