hdu 4005 Number String 数列上的DP 【From:2011 Asia Dalian Regional Contest】&& 【SGU489】 Extremal Permutations

题意:一个1~n的序列,分别用'I','D'表示序列的升降,如{1,2,4,3},则是属于IID的情况,而1~4中属于IID情况的可以有很多不同的序列,给出序列的I、D表现形式(‘?’表示‘I’或‘D’都可以),求出满足这样要求的有多少种。

例如:

Sample Input
II
ID
DI
DD
?D
??
 

Sample Output
1
2
2
1
3
6
Hint

Permutation {1,2,3} has signature "II".
Permutations {1,3,2} and {2,3,1} have signature "ID".
Permutations {3,1,2} and {2,1,3} have signature "DI".
Permutation {3,2,1} has signature "DD".
"?D" can be either "ID" or "DD".
"??" gives all possible permutations of length 3.
 

可以用dp[i][j]表示:处理完第i位,序列末尾位j的序列共有多少个。最后的结果为sigma{dp[N][i]},1≤i≤N

处理dp[1~i][]的过程中i是依次1~n相加。处理完dp[i-1][]后,加入的数即为i,而dp[i][j]是要将i放进去j换出来,而这里有一种将i放进去j换出来,同时不影响升降顺序的方法是:

将dp[i-1][j]的i-1个数的序列中 ≥j 的数都加1,这样i-1变成了i,j变成了j+1,而j自然就补在后面了。

所以对”ID“序列依次处理即可,初始条件:dp[1][1] = 1; 即只有{1}。

处理‘I’:dp[i][j] = sigma{dp[i-1][x]},其中1≤x≤j-1,可进一步简化,dp[i][j] = dp[i-1][j-1]+dp[i-1][j-1]

处理‘D’:dp[i][j] = sigma{dp[i-1][x]},其中j≤x≤i-1,可进一步简化,dp[i][j] = dp[i-1][j+1]+dp[i-1][j]

处理‘?’:dp[i][j] = sigma{dp[i-1][x]},其中1≤x≤i-1,直接计算

处理过程中在注意边界和取模问题就可以了,附上代码

View Code
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstdlib>
 4 #include <cstring>
 5 #include <algorithm>
 6 #include <cmath>
 7 #include <string>
 8 #include <vector>
 9 #include <queue>
10 #include <set>
11 #include <map>
12 #define MAXN 1010
13 using namespace std;
14 const int mod = 1000000007;
15 char str[MAXN];
16 
17 int dp[MAXN][MAXN];
18 int main(){
19     int N;
20     char ch;
21     while(~scanf("%s",str)){
22         N=strlen(str)+1;
23         memset(dp,0,sizeof(dp));
24         dp[1][1]=1;
25         for(int i=2;i<=N;i++){
26             ch = str[i-2];
27             if(ch=='?'){
28                 int sum=0;
29                 for(int j=1;j<=i-1;j++)sum = (sum+dp[i-1][j])%mod;
30                 for(int j=1;j<=i;j++){
31                     dp[i][j]=sum;
32                 }
33             }
34             else if(ch=='I'){
35                 for(int j=2;j<=i;j++){
36                     dp[i][j]=(dp[i][j-1]+dp[i-1][j-1])%mod;
37                 }
38             }
39             else{
40                 for(int j=i-1;j>=1;j--){
41                     dp[i][j]=(dp[i][j+1]+dp[i-1][j])%mod;
42                 }
43             }
44         }
45         int res=0;
46         for(int i=1;i<=N;i++){
47             res = (res+dp[N][i])%mod;
48         }
49         printf("%d\n",res);
50     }
51     return 0;
52 }

另外一道题SGU489,求法和上道题类似~~【该死去年过的大连赛区的题,今年做这题居然想不起来】

题意:给出一个n,求1~n的排列中,满足IDIDID……或DIDIDID……的序列有多少个。

dp[i][j][0]:处理完第i个数后最后一位数是j,且倒数第二位至最后一位的趋势是下降;

dp[i][j][1]:处理完第i个数后最后一位数是j,且倒数第二位至最后一位的趋势是上升。

 转移方程:

dp[i][j][0] = sigma{dp[i-1][x][1]},其中j≤x≤i-1,可进一步简化,dp[i][j][0] = dp[i-1][j+1][0]+dp[i-1][j][1]

dp[i][j][1] = sigma{dp[i-1][x][0]},其中1≤x≤j-1,可进一步简化,dp[i][j][1] = dp[i-1][j-1][1]+dp[i-1][j-1][0]

最后求sigma{dp[N][i][0]+dp[N][i][1]},1≤i≤N。注意初始条件为dp[1][1][0] = dp[1][1][1] = 1;所以n==1时要特判一下。当然还需要用到滚动数组优化一下空间,上一题其实也可以用滚动数组,不过范围不大也没必要,代码如下:

Time: 953 MS

View Code
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<algorithm>
 5 #include<cstring>
 6 using namespace std;
 7 
 8 int dp[2][10005][2];
 9 int main(){
10     int n, m, i, j;
11     int ans;
12     while(~scanf("%d%d",&n,&m)){
13         if(n==1){
14             printf("%d\n",n%m);continue;
15         }
16         memset(dp,0,sizeof(dp));
17         dp[1][1][0] = dp[1][1][1] = 1;
18         for(i=2;i<=n;i++){
19             for(j=i;j>=1;j--){
20                 dp[i&1][j][0] = (dp[i&1][j+1][0]+dp[(i-1)&1][j][1])%m;
21             }
22             for(j=1;j<=i;j++){
23                 dp[i&1][j][1] = (dp[i&1][j-1][1]+dp[(i-1)&1][j-1][0])%m;
24             }
25         }
26         ans = 0;
27         for(j=1;j<=n;j++){
28             ans = (ans+(dp[n&1][j][0]+dp[n&1][j][1])%m)%m;//这里很坑爹,一定要两个相加后取模,不然就暴int了
29 //当然如果直接用long long就不会了,不过dp数组用long long的超空间问题不能实现,那么三个连加就不知道什么情况了。
30         }
31         printf("%d\n",ans%m);
32     }
33     return 0;
34 }

还有一个优化版本,利用的性质是对称性:dp[i][j][0] = dp[i][i-j][1]。

最后求sigma{dp[N][i][0]+dp[N][i][1]},1≤i≤N。也就是sigma{dp[N][i][0]}*2了,所以可以少计算dp[i][j][0]。

Time: 500 MS

View Code
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<algorithm>
 5 #include<cstring>
 6 using namespace std;
 7 
 8 int dp[2][10005];
 9 int main(){
10     int n, m, i, j;
11     int ans;
12     while(~scanf("%d%d",&n,&m)){
13         if(n==1){
14             printf("%d\n",n%m);continue;
15         }
16         memset(dp,0,sizeof(dp));
17         dp[1][1] = 1;
18         for(i=2;i<=n;i++){
19             for(j=i;j>=1;j--){
20                 dp[i&1][j] = (dp[i&1][j+1]+dp[(i-1)&1][i-j])%m;
21             }
22         }
23         ans = 0;
24         for(j=1;j<=n;j++){
25             ans = (ans+dp[n&1][j])%m;
26         }
27         printf("%d\n",(ans+ans)%m);
28     }
29     return 0;
30 }

 

 
原文地址:https://www.cnblogs.com/celia01/p/2611017.html