FZU - 2218 Simple String Problem
题目大意:给一个长度为n含有k个不同字母的串,从中挑选出两个连续的子串,要求两个子串中含有不同的字符,问这样的两个子串长度乘积最大是多少?
根据题目所给的k<=16很自然的想到用状压dp来处理,但不知道该dp个什么,在观摩大佬的做法后才明白。我们用0000000000000000到1111111111111111表示pomnlkjhgfedcba的字符存不存在的状态,某个字符存在的话对应位就是1,反之就是0。一开始dp[x]就表示,在给出的串中状态x的最长长度,这样很好的处理重复的字符了,比如像aab,abaab,ab,ba这些串,他们的状态都是3(也就是a位和b位是1其他全0),然后dp[3]就是5(abaab这个)。所以我们遍历一遍S的所有子串就可以得到所有状态在子串中的最长长度。
因为是两个含有不同字符的子串长度相乘,假设目前k是4,然后一个子串是的状态是1001,那么和它含有不同字符的子串的状态分别是0010,0100,0110,也就是1001反状态0110的所有子状态,我们直接让dp[x]去和它所有反状态的子状态相乘的话就会有很多种组合,处理上就非常的麻烦,所以这时就有个优化,让dp[x]表示它和它子串中最长的长度,也就是1001,包含了0001,1000,1001这几种状态的情况,然后在判断答案时直接让dp[x]和它的反状态dp[(1<<k)-1-i](全1减去x)相乘就包含了它们各自子串的情况。具体的细节如代码。
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 const int N=(1<<16)+18; 5 int dp[N]; 6 char s[2118]; 7 int main() 8 { 9 int n,k,t; 10 scanf("%d",&t); 11 while(t--) 12 { 13 scanf("%d%d",&n,&k); 14 scanf("%s",s); 15 for(int i=0;i<(1<<k);i++) 16 dp[i]=0;//初始化所有状况的初始最长长度 17 for(int i=0;i<n;i++)//遍历所有子串的状态 18 { 19 int x=0; 20 for(int j=i;j<n;j++) 21 { 22 x|=1<<(s[j]-'a');//因为每个字符不管它在这个子串中重复出现了几次 23 //只要出现了一次,对于位置就是1,所以直接对位与| 24 dp[x]=max(dp[x],j-i+1); 25 } 26 } 27 for(int i=0;i<(1<<k);i++)//让每个状态包含它的子状态 28 for(int j=0;j<k;j++) 29 if(i&(1<<j))//对位是1的异或取反 30 dp[i]=max(dp[i],dp[i^(1<<j)]); 31 //因为像10110包含了10100的状态,而10100已经包含了10000和00100 32 //所以只需要有1的位置对位异或,10110就能包含它所有子串的状态 33 int ans=0; 34 for(int i=0;i<(1<<k);i++) 35 ans=max(ans,dp[i]*dp[(1<<k)-1-i]);//一个串的结果和它相反串的结果相乘 36 printf("%d ",ans); 37 } 38 return 0; 39 }