1. 程式人生 > >計數dp-hdu-4054-Number String

計數dp-hdu-4054-Number String

題目連結:

題目大意:

給一個只含‘I','D','?'三種字元的字串,I表示當前數字大於前面的數字,D表示當前的數字小於前面一位的數字,?表示當前位既可以小於又可以大於。

問1~n的排列中有多少個滿足該字串。

解題思路:

計數dp.

dp[i][j]表示長度為i字串,最後一個數為j時,能達到滿足給定字串的1~i+1的排列個數。

轉移方程:

當S[i]='I'時,當前如果為j的話,前面的一位肯定要小於j,dp[i][j]+=Mi[j-1] ; //Mi[i]表示前面一位下標小於等於i dp[][1~i]的和

當S[i]='D'時,dp[i][j]+=(la-Mi[j-1]);  //前一位要大於等於j

當S[i]='?'時,dp[i][j]+=la; //la表示前一位所有的狀態總和.

注意遞推的時候,前i位只和相對大小有關,與絕對大小無關,所以都用1~i表示,當增加一位時,就變成了1~i+1,如果當前為j,前面的小於j的狀態不變,大於等於j的狀態k所表示的值 現在表示k+1的值,這樣就從1~i等價的轉化成了1~i+1,這樣考慮就不用考慮1~n的放法了,遞推到n位時肯定都是1~n了。

程式碼:

#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cctype>
#include<map>
#include<vector>
#include<set>
#include<queue>
#include<string>
using namespace std;

const int INF = 0x3f3f3f3f;
const double eps = 1e-6;
const double PI = acos(-1.0);
typedef __int64 ll;
//#pragma comment(linker, "/STACK:1024000000,1024000000")
/*
freopen("data.in","r",stdin);
freopen("data.out","w",stdout);
*/

#define M 1000000007
#define Maxn 1100
char sa[Maxn];
int dp[Maxn][Maxn]; //dp[i][j]表示有i個字元,當前數字為j時,所有的總數
                    //注意有i個字元說明前面一共有1~i+1個不同的數字,dp[i-1]共有1~i個數字,
                    //建立遞推時,如果當前為j,則前面小於j的不變,大於等於j的全部加一,這樣就從1~i變成了1~i+1
int la;
int Mi[Maxn][2]; //Mi[i]表示上一個dp[i-1]下標小於等於j的值的總和

int main()
{
    while(~scanf("%s",sa+1))
    {
        int len=strlen(sa+1);
        for(int i=0;i<=len+10;i++) //初始化
        {
            Mi[i][0]=Mi[i][1]=0;
            for(int j=0;j<=len+10;j++)
                dp[i][j]=0;
        }
        dp[0][1]=1; //沒有字元時,有一個數 且這個數為1
        la=1;
        Mi[1][0]=1; //下標小於等於1的有一個
        int pp=0;

        for(int i=1;i<=len;i++)
        {
            pp=pp^1; //當前狀態
            int n=i+1;//當前為可能的取值,注意前面的之和相對大小有關
            ll cur=0;
            for(int j=1;j<=n;j++) //列舉當前位的數值
            {
                if(sa[i]=='I') //如果為增的話,前面的肯定是小於j的
                    dp[i][j]=(dp[i][j]+Mi[j-1][pp^1])%M;
                else if(sa[i]=='D') //減的話,前面是大於等於j的,加1後就變成了大於j的了
                    dp[i][j]=(dp[i][j]+(la-Mi[j-1][pp^1])%M+M)%M;
                else //如果無所謂的話,前面所有1-n-1個狀態都行
                    dp[i][j]=(dp[i][j]+la)%M; //用一個la記錄
                Mi[j][pp]=(Mi[j-1][pp]+dp[i][j])%M;//求出當前的Mi
                cur=(cur+dp[i][j])%M; //求出當前的la
            }
            la=cur; //這題卡常數,多了幾個迴圈都不行
        }
        ll ans=0;
        for(int i=1;i<=len+1;i++) //列舉最後的能夠佔有的值
            ans=(ans+dp[len][i])%M;
        printf("%d\n",ans);
    }
    return 0;
}