题意:
给你一个含n个字符的字符串,字符为'D'时表示小于号,字符为“I”时表示大于号,字符为“?”时表示大小于都可以。比如排列 {3, 1, 2, 7, 4, 6, 5} 表示为字符串 DIIDID。任务是计算所有能产生给定字符串的序列数量,每个序列含n+1个数字,分别为1~n+1,即从1开始且不重复。
思路:DP计数。如下步骤
1)将规模n降低,使得对于每个i (1<=i<=n)都可以依靠i-1的结果来计算。最小规模为1个符号,决定两个数字的序列。
2)考虑对于具有i个数字的序列(值从1~i),指定第i个数字为j (1<=j<=i+1),计算所产生的序列数量。那么dp[i][j]表示着具有i个数字的序列,若最后一个数字为j时,所有可能的序列数量。那么dp[i][i+1]就统计了具有i个数字的且满足前i个要求的序列数量。
3)分两种情况:
第一种是第i个为I(小于号,升序):dp[i][j] = dp[i-1][j-1] + dp[i-1][j-2] + .. + dp[i-1][1]。
另一种是第i个为D(大于号,降序): dp[i][j] = dp[i-1][i] + dp[i-1][i-1] + ... + dp[i-1][j]。
分析第一个式子,假设前面i个数字所能产生的可能已经知道,那么直接将j加在最后面就行了。比如:序列3>2<4>1 共4个数字和3个符号,假设第4个符号为<,那么现在考虑第5个数字,要算的有dp[4][1] dp[4][2] dp[4][3] dp[4][4] dp[4][5]共5个。dp[4][1]可以想象将1接在序类后,即 3 2 4 1 1,疑问?这不是重复了吗?这样子我们可以将前4个数字中所有大于第5个数字的都加1,就变成4 3 5 2 1,这个序列就合法了吧?是的,对于所有的4个数字的合法序列都这么做。前提是对于第4个数字小于5的所有4数字序列才可以这么考虑。大于等于5的序列再来加1也必定重复。
分析第二个式子,同样,假设前面i个数字所能产生的可能已经知道,也是加在后面。比如:序列3>1<2 共3个数字和2个符号,假设第3个符号为>,那么现在考虑第4个数字,2大于1,同时还得考虑如上“加一”情况,所以1和2都要考虑。那么要计算的有dp[3][1] dp[3][2] 共2个,dp[3][3]对于我们所假设的情况是非法的。因为大于等于2的都要加一,所以dp[3][2]可能产生的序列之一为 { 3+1, 1,2+1, 2 } 即 { 4, 1,3 ,2 } 。合法!对于其他情况依此法计算。
#include <iostream> #include <stdio.h> #include <string.h> #include <stdlib.h> using namespace std; const int N=1005; const int mod=1000000007; unsigned int sum[N][N]; char s[N]; int main() { //freopen("input.txt","r",stdin); while(cin>>s) { memset(sum, 0, sizeof(sum)); int len=strlen(s); sum[0][1]=1; for(int i=1; i<=len; i++) //对于len个符号,有len+1个数字组成。 { int siz=i+2; if(s[i-1]=='I') { for(int j=1; j<siz; j++) //从小往大的方向累加。 { sum[i][j]=sum[i][j-1]; sum[i][j]=((long long)sum[i][j]+ sum[i-1][j-1])%mod; } } else if(s[i-1]=='D') { for(int j=i+1; j>0; j--) //从大往小的方向累加。 { sum[i][j]=sum[i][j+1]; sum[i][j]=((long long)sum[i][j]+sum[i-1][j])%mod; } } else //问号,将上一个位置所有可能相加。 { long long tmp=0; for(int j=1; j<=i; j++) tmp=((long long)tmp+sum[i-1][j])%mod; for(int j=1; j<siz; j++) sum[i][j]=tmp; } } long long ans=0; for(int i=1; i<=len+1; i++) //累计所有可能。 ans=((long long)ans+sum[len][i])%mod; cout<<ans<<endl; } return 0; } int型代码