描述
给定字符串,求它的回文子序列个数。回文子序列反转字符顺序后仍然与原序列相同。例如字符串aba中,回文子序列为"a", "a", "aa", "b", "aba",共5个。内容相同位置不同的子序列算不同的子序列。
输入
第一行一个整数T,表示数据组数。之后是T组数据,每组数据为一行字符串。
输出
对于每组数据输出一行,格式为"Case #X: Y",X代表数据编号(从1开始),Y为答案。答案对100007取模。
数据范围
1 ≤ T ≤ 30
小数据
字符串长度 ≤ 25
大数据
字符串长度 ≤ 1000
样例输入:
5 aba abcbaddabcba 12111112351121 ccccccc fdadfa样例输出:
Case #1: 5 Case #2: 277 Case #3: 1333 Case #4: 127 Case #5: 17
动态规划的题,一开始想了很久,没想出来。后来经朋友指点,发现是自己思路不对。不应该从头尾一起算,直接从左到右,老老实实建表,更新结果就可以做出来了。
从字符串左边开始,每添加一个字符就更新一次表,表中保存的是该字符后的子串包含的回文序列数。
以样例的fdadfa为例:
初始化: res=0 表为空
输入f: 将f加入表,map[0].char = 'f', f后面的子串为空,map[0].num=0
| char | 后面子串 | num | res |
0 | f | “” | 0 | 1 |
string = "f", res = 1
输入d: 将d加入表,map[1].char = 'd',map[1].num=0,
f后面的子串为d,map[0].num=1
| char | 后面子串 | num | res |
0 | f | "d" | 1 | |
1 | d | "" | 0 | 2 |
string = "fd", res = 2
输入a: 将a加入表,
| char | 后面子串 | num | res |
0 | f | "da" | 2 | |
1 | d | "a" | 1 | |
2 | a | "" | 0 | 3 |
string = "fda", res = 3
输入d:将a加入表,map[3].char = 'd',map[3].num=0,
a后面的子串为d,map[2].num=1;
d后面的子串为ad,map[1].num=2
f后面的子串为dad,map[0].num=5,
| char | 后面子串 | num | res |
0 | f | "dad" | 5 | |
1 | d | "ad" | 2 | |
2 | a | "d" | 1 | |
3 | d | "" | 0 | 6 |
注意到这个时候因为新加入的第四个字符d与前面的第二个字符d相同,所以f后面的子串dad包含了5个回文子序列,如果第四个字符不是d,而是其他不重复的字符,此时map[0].num应该等于3,所以多出来的两个回文子序列是dd以及夹在dd中间的回文子序列个数,即更新前的map[1].num
string ="fdad",同理res本来应该等于4,因为遇到相同字符,此时多出来的也是dd和夹在dd中间的回文子序列个数,
故res=3(上一次结果)+1(末尾新加入字符)+1(dd)+1(夹在dd中间的回文子序列个数map[1].num)=6
而且我们看到,计算res是用表格更新前的map[1].num,所以应该先计算res,再更新表
输入f:
string ="fdadf",res = 6(上一次)+1(末尾新加入字符)+1(ff)+5(夹在ff之间的回文子序列个数map[0].num)=13
然后更新表:
| char | 后面子串 | num | res |
0 | f | "dadf" | 6 | |
1 | d | "adf" | 3 | |
2 | a | "df" | 2 | |
3 | d | "f" | 1 | |
4 | f | "" | 0 | 13 |
最后输入a:
string ="fdadfa",res = 13(上一次)+1(末尾新加入字符)+1(aa)+2(夹在aa之间的回文子序列个数map[2].num)=17
然后更新表:
| char | 后面子串 | num | res |
0 | f | "dadfa" | 10 | |
1 | d | "adfa" | 7 | |
2 | a | "dfa" | 3 | |
3 | d | "fa" | 2 | |
4 | f | "a" | 1 | |
5 | a | "" | 0 | |
将a加入表,map[5].char ='a',map[5].num=0;
我们用一个累计变量new(代码中的get_sum)表示因为末尾添加了新字符而新增的回文子序列个数,此时new=1,更新公式map[i].num+=new
f后面子串为a,更新前map[4].num =0; 更新后map[4].num = 1;
d后面子串为fa,更新前map[3].num = 1,此时new=1,更新后map[3].num=2;
a后面子串为dfa,更新前map[2].num=2,此时new=1,更新后map[2].num = 3;
因为当前字符a与新加入字符a相同,所以从下面的开始,new = 1(新加入字符a)+1(aa)+2(夹在aa之间的回文子序列个数map[2].num)=4
d后面子串为adfa,更新前map[1].num=3,此时new=4,更新后map[1].num = 3 + new =7;
f后面子串为dadfa,更新前map[0].num=6,此时new=4,更新后map[1].num = 6 + new =10;
#include <vector>
#include <iostream>
#include <string>
using namespace std;
struct Node {
int num;
char my_char;
};
int huiWenNum(string s){
vector<Node> vec;
int i,j,res=0, get_sum=0;
for(i=0;i<s.size();i++){
res++;
if(!vec.empty()){
for(j=vec.size()-1;j>=0;j--){
if(vec[j].my_char==s[i]){
res = res+1+vec[j].num;
}
}
}
Node tmp;
tmp.my_char=s[i];
tmp.num=0;
vec.push_back(tmp);
if(vec.size()>1){
get_sum=1;
for(j=vec.size()-2;j>=0;j--){
if(vec[j].my_char!=s[i]){
vec[j].num+=get_sum;
}else{//meet the same char
int tmp_num = vec[j].num;//storage the num for plus
vec[j].num+=get_sum;
get_sum = get_sum+1+tmp_num;//
}
}
/*cout<<"the map is:"<<endl;
for(j=0;j<vec.size();j++){
cout<<vec[j].my_char<<' '<<vec[j].num<<endl;
}*/
}
}
return res%100007;
}
int main()
{
int i,nSample,res;
string s;
cin>>nSample;
int head,tail,j,k;
for (i=0;i<nSample;i++){
cin>>s;
res = huiWenNum(s);
cout<<"Case #"<<i+1<<": "<<res<<endl;
}
//system("pause");
return 0;
}
这道题我只在小数据AC,大数据没有过。最后取模100007的步骤做得太轻率了,求res和更新表格的时候,每超过100007就取一次模可能好一些。毕竟是结果是指数级别,int存不下那么大的数字。