问题描述
小蓝特别喜欢单调递增的事物。
在一个字符串中,如果取出若干个字符,将这些字符按照在字符串中的顺序排列后是单调递增的,则成为这个字符串中的一个单调递增子序列。
例如,在字符串 lanqiao 中,如果取出字符 n 和 q,则 nq 组成一个单调递增子序列。类似的单调递增子序列还有 lnq、i、ano 等等。
小蓝发现,有些子序列虽然位置不同,但是字符序列是一样的,例如取第二个字符和最后一个字符可以取到 ao,取最后两个字符也可以取到 ao。小蓝认为他们并没有本质不同。
对于一个字符串,小蓝想知道,本质不同的递增子序列有多少个?
例如,对于字符串 lanqiao,本质不同的递增子序列有 21 个。它们分别
是 l、a、n、q、i、o、ln、an、lq、aq、nq、ai、lo、ao、no、io、lnq、
anq、lno、ano、aio。
请问对于以下字符串
tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhfiadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqijgihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmadvrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl
本质不同的递增子序列有多少个?
解题思路
第一种
先看一组例子,试图找出点规律。
a: a
ab: a,b,ab
abc: a,b,ab,c, ac,bc,abc
abcd:a,b,ab,c,ac,bc,abc,d,ad,bd,cd,abd,acd,bcd,abcd
用dp(a)表示字母a结尾的本质不同的递增子序列个数
dp(a)=1,dp(b)=2,p©=4,dp(d)=8
以此类推
可得:dp(x)=dp(a)+…+dp(x-1)+1
而总共的递增子序列的个数为:dp(a)+dp(b)+dp©+dp(d)=15
考虑重复字母的影响
a: a
ac: a,c,ac
acb: a,c,ac,b,ab
acbc: a,c,ac,b,ab,bc,abc
acbcb: a,c,ac,b,ab,bc,abc
dp(a)=1,dp(c1)=2,dp(b1)=2,dp(c2)=4,dp(b2)=2
=>dp(c1)=dp(a)+1=2,dp(b1)=dp(a)+1=2,dp(c2)=dp(a)+dp(b1)+1=4,dp(b2)=dp(a)+1=2
由于c和b出现了两次,我们只要最后一次的值就好,因为后面出现的b和c能组成的子序列包含了前面b和c能出现的子序列
而总共的递增子序列的个数为:dp(a)+dp(b2)+dp(c2)=7
代码
#include<iostream>
using namespace std;
int dp[205];//共200个小写英文字母
int main(){
string s;
cin>>s;
for(int i=0;i<s.length();i++){
int use[26]={0};//标志字母是否重复出现
dp[i]=1; //递推得出+1
for(int j=i-1;j>=0;j--){//一定是从右到左,不然会漏
//与前面没有重复的,而且前面的字母要比现在遍历的字母小
if(s[j]<s[i] && !use[s[j]-'a']){
dp[i]+=dp[j];
use[s[j]-'a']=1;
}
}
}
int use[26]={0};
int ans=0;
for(int i=s.length()-1;i>=0;i--){
if(!use[s[i]-'a']){ //除去重复的子序列
ans+=dp[i];
use[s[i]-'a']=1;
}
}
cout<<ans<<endl; //3616159
return 0;
}
第二种
写出递推公式
if(s[i]>s[j])
dp[i]+=dp[j]; //前字母小,加上
else if(s[i]==s[j])
dp[i]-=dp[j]; //重复的就删掉
代码
#include<iostream>
using namespace std;
int dp[205];//共200个小写英文字母
int main(){
string s;
cin>>s;
for(int i=0;i<s.length();i++){
dp[i]=1;
for(int j=0;j<i;j++){
if(s[i]>s[j]){
dp[i]+=dp[j];
}else if(s[i]==s[j]){
dp[i]-=dp[j];
}
}
}
int ans=0;
for(int i=0;i<s.length();i++){
ans+=dp[i];
}
cout<<ans;
return 0;
}