回文字符序列——微软编程之美2015资格赛第2题题解

描述

给定字符串,求它的回文子序列个数。回文子序列反转字符顺序后仍然与原序列相同。例如字符串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;
由以上分析可以得到res和new两个变量的更新公式,用代码实现如下:

#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存不下那么大的数字。
发布了29 篇原创文章 · 获赞 28 · 访问量 13万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览