Mountain Subsequences
Problem Description
Coco is a beautiful ACMer girl living in a very beautiful mountain. There are many trees and flowers on the mountain, and there are many animals and birds also. Coco like the mountain so much that she now name some letter sequences as Mountain Subsequences.
A Mountain Subsequence is defined as following:
1. If the length of the subsequence is n, there should be a max value letter, and the subsequence should like this, a1 < ...< ai < ai+1 < Amax > aj > aj+1 > ... > an
2. It should have at least 3 elements, and in the left of the max value letter there should have at least one element, the same as in the right.
3. The value of the letter is the ASCII value.
Given a letter sequence, Coco wants to know how many Mountain Subsequences exist.
Input
For each case there is a number n (1<= n <= 100000) which means the length of the letter sequence in the first line, and the next line contains the letter sequence.
Please note that the letter sequence only contain lowercase letters.
Output
Example Input
4abca
Example Output
4
Hint
aba, aca, bca, abca
Author
给你一个长度为n的字符串仅由小写英文字母组成,求满足
a1 < ...< ai < ai+1 < Amax > aj > aj+1 > ... > an
的子串的个数,其实也就是统计所有满足以某一元素为中心左边递增,右边递减的子串的数目,要求该子串
最小长度为3,中心元素左右都至少有一个元素。
题目告诉我们输入串仅包含26个英文字母,这样我们只要枚举每一个位置,然后记录每个位置左边满足
条件的个数,右边满足条件的个数,最后乘起来就是了。关键是我们如何统计左右两边满足的个数呢?
本能的反应告诉我可以用DP来做,主要还是因为有之前做过的求最长递增子序列的基础。可是,
老师告诉我说,这不叫DP,看代码会发现,其实就是个递推求解关系式。我总是觉得,之所以能想到
这里,完全是应用了DP的阶段划分的思想,暂且不论也罢。那么,怎么划分阶段呢?我们定义
数组dp[26],dl[maxn],dr[maxn],dp[c]表示以字符c结尾的满足情况的个数,dl[i]表示第i个位置左边满足
条件的个数,dr[i]表示第i个位置右边满足条件的个数。当然,我们可以发现,dp[s[i]] = (dl[i] + 1),s[i]= c;
1表示s[i]单独一个时满足的情况,dl[i]表示他左边的满足的各种情况加上s[i] 后满足的情况。
注意:
有细心的同学会问,在一个串中如果有相同的字母,那么他们的dp值是否相同呢?答案就在题意中给出了。
题目在要求该子串的时候用了
a1 < ...< ai < ai+1 < Amax > aj > aj+1 > ... > an
这样一个式子,每个元素都表示不同位置的元素,也就是说,在串aaba中应该有aba,aba两个答案,
而不是只有aba一个。因为前面两个子串形式上看虽然相同,但纠其本质,a和a的来源不同,
可以表示为a1ba和a2ba两个答案。(参考网址和官方标程和测试数据:http://www.cnblogs.com/jeff-wgc/p/4464169.html)
#include <iostream> #include <stdio.h> #include <algorithm> #include <string.h> #include <cmath> #include <stdlib.h> #include <map> #include <vector> #include <set> #include <stack> #include <queue> #include <cctype> using namespace std; int n; const int maxn=100010; char s[maxn]; int dp[maxn];///dp[i]是以第i个字母结尾的递增子序列的个数(子序列的长度>=2) int has[27];///has[i]是以字母'a'+i结尾的递增子序列的个数(子序列的长度>=1,等于1的情况就是它本身) const int mod=2012; ///对于dp数组,枚举每个位置作为最高峰, 从左到右求一遍,再从右到左求一遍, 则以该位置作为最高峰符合题意的串 ///的个数为 dp[i]*dp[i’] , 最终答案为每个位置累加。 int main(int argc, char *argv[]) { while(scanf("%d",&n)!=EOF) { scanf("%s",s); long long ans=0; memset(has,0,sizeof(has)); for(int i=0;i<n;i++) { int id=s[i]-'a'; dp[i]=0; for(int j=0;j<id;j++) dp[i]=(dp[i]+has[j])%mod; has[id]= ( has[id]+dp[i]+1 ) %mod; } memset(has,0,sizeof(has)); long long sum;//相当于上头的dp数组。 for(int i=n-1;i>=0;i--) { int id=s[i]-'a'; sum=0; for(int j=0;j<id;j++) sum=( sum+has[j] ) %mod; has[id]= ( has[id]+sum+1 ) % mod ; ans=( ans+sum*dp[i] ) % mod; } cout<<ans<<endl; } return 0; }
另外一种思路:
DP[i][j]表示前i个字符,以j为结尾的上升序列个数,正着和反着都做一遍,然后枚举中点,两边一组合就行了。复杂度O(n*26)。其实可以降到1维,顺序扫i的时候只需要查询前面val小于val[i]的个数就可以了,然后再更新。这样写起来就很像线段树了。
#include <map> #include <set> #include <list> #include <queue> #include <stack> #include <cmath> #include <ctime> #include <vector> #include <bitset> #include <cstdio> #include <string> #include <numeric> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <functional> using namespace std; typedef long long ll; typedef unsigned long long ull; #define inf 0x7fffffff #define read freopen("in.txt","r",stdin) #define write freopen("out.txt","w",stdout) #define maxn 100010 #define MOD 2012 int dx[4]= {-1,1,0,0}; int dy[4]= {0,0,-1,1}; //up down left right char ss[maxn]; int dp[maxn][30],dp2[maxn][30];///dp[i][j]代表前i个单词权值小于等于j的上升序列方案数,dp2同理,只不过是反过来 int main() { //read; int n; while(~scanf("%d",&n)) { scanf("%s",ss); memset(dp,0,sizeof(dp)); memset(dp2,0,sizeof(dp2)); ///---------正向---------- for(int i=1; i<=n; ++i) { int t=ss[i-1]-'a'+1; dp[i][t]=1; ///自身算作一个 for(int j=1; j<t; ++j) dp[i][t] = ( dp[i][t] + dp[i-1][j] ) % MOD; ///加上前一个字符为结尾的 for(int j=1; j<=26; ++j) dp[i][j] = ( dp[i][j] + dp[i-1][j] ) % MOD; } for(int i=1; i<=n; ++i) for(int j=1; j<=26; ++j) dp[i][j]=(dp[i][j]+dp[i][j-1])%MOD; ///--------逆向--------- for(int i=n; i>0; --i) { int t=ss[i-1]-'a'+1; dp2[i][t]=1; for(int j=1; j<t; ++j) dp2[i][t]=(dp2[i][t]+dp2[i+1][j])%MOD; for(int j=1; j<=26; ++j) dp2[i][j]=(dp2[i][j]+dp2[i+1][j])%MOD; } for(int i=1; i<=n; ++i) for(int j=1; j<=26; ++j) dp2[i][j]=(dp2[i][j]+dp2[i][j-1])%MOD; ///--------求和-------- int ans=0; for(int i=2; i<n; ++i) { int t=ss[i-1]-'a'+1; ans=(ans+dp[i-1][t-1]*dp2[i+1][t-1])%MOD; } printf("%d ",ans); } return 0; }
线段树解决方式:
#include <map> #include <set> #include <list> #include <queue> #include <stack> #include <cmath> #include <ctime> #include <vector> #include <bitset> #include <cstdio> #include <string> #include <numeric> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <functional> using namespace std; typedef long long ll; typedef unsigned long long ull; #define inf 0x7fffffff #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define root 1,26,1 #define maxn 100005 #define maxm 30 #define MOD 2012 int dx[4]={-1,1,0,0}; int dy[4]={0,0,-1,1};//up down left right bool inmap(int x,int y,int n,int m){if(x<1||x>n||y<1||y>m)return false;return true;} int hashmap(int x,int y,int m){return (x-1)*m+y;} int sum[maxm<<2]; void pushup(int rt) { sum[rt]=(sum[rt<<1]+sum[rt<<1|1])%MOD; return ; } void build(int l,int r,int rt) { if(l==r) { sum[rt]=0; return ; } int m=(l+r)>>1; build(lson); build(rson); pushup(rt); } void update(int p,int v,int l,int r,int rt) { if(l==r) { sum[rt]=(sum[rt]+v)%MOD; return ; } int m=(l+r)>>1; if(p<=m)update(p,v,lson); else update(p,v,rson); pushup(rt); } int query(int l1,int r1,int l,int r,int rt) { if(l1<=l&&r1>=r) return sum[rt]; int m=(l+r)>>1,ans=0; if(l1<=m)ans=(ans+query(l1,r1,lson))%MOD; if(r1>m)ans=(ans+query(l1,r1,rson))%MOD; return ans; } char ss[maxn]; int tot[maxn][2]; int main() { //read; int n; while(~scanf("%d",&n)) { scanf("%s",ss+1); memset(tot,0,sizeof(tot)); build(root); for(int i=1;i<=n;++i) { int p=ss[i]-'a'+1,t; if(p==1)t=1; else t=query(1,p-1,root)+1; update(p,t,root); tot[i][0]=(t-1+MOD)%MOD; } build(root); for(int i=n;i>0;--i) { int p=ss[i]-'a'+1,t; if(p==1)t=1; else t=query(1,p-1,root)+1; update(p,t,root); tot[i][1]=(t-1+MOD)%MOD; } int ans=0; for(int i=2;i<n;++i) ans=(ans+tot[i][0]*tot[i][1])%MOD; printf("%d ",ans); } return 0; }