1. 程式人生 > >【[CQOI2016]手機號碼】

【[CQOI2016]手機號碼】

遞推版的數位dp

絕對的暴力美學

我們設\(dp[l][i][j][0/1][0/1][0/1]\)表示到了第\(l\)位,這一位上選擇的數是\(i\)\(l-1\)位選擇的數是\(j\),第一個\(0/1\)代表\(4\)沒有/有出現過,第二個\(0/1\)代表\(8\)沒有/有出現過,第三個\(0/1\)代表連續三位沒有/有出現過

於是轉移很簡單了

但是卡位實在是鬼畜

我卡位的方式有些鬼畜,所以細節非常的多

#include<iostream>
#include<cstring>
#include<cstdio>
#define re register
#define maxn 16
#define LL long long
LL L,R;
int a[maxn],num;
LL dp[maxn][11][11][2][2][2];
//位數,這一位上的數,上一位的數,0/1表示4/8有/沒有出現,0/1表示有/沒有連續三位 
inline LL slove(LL x)
{
    memset(dp,0,sizeof(dp));
    memset(a,0,sizeof(a));
    num=0;
    while(x)
    {
        a[++num]=x%10;
        x/=10;
    }//分解數位
    a[num+1]=-11,a[num+2]=11;
    for(re int i=0;i<=9;i++)
        for(re int j=0;j<=9;j++)
            for(re int k=0;k<=9;k++)
            {
                int opt_4=0,opt_8=0;
                if(i==4||j==4||k==4) opt_4=1;
                if(i==8||j==8||k==8) opt_8=1;
                if(i==j&&j==k) dp[3][i][j][opt_4][opt_8][1]+=1;
                else dp[3][i][j][opt_4][opt_8][0]+=1;
            }//先初始化dp[3]之後再往下推
    for(re int l=3;l<num;l++)//刷錶轉移
        for(re int i=0;i<=9;i++)
            for(re int j=0;j<=9;j++)
                for(re int k=0;k<=9;k++)
                    for(re int o4=0;o4<=1;o4++)
                        for(re int o8=0;o8<=1;o8++)
                            for(re int o=0;o<=1;o++)
                                dp[l+1][i][j][(i==4)||o4][(i==8)||o8][((i==j)&&(j==k))||o]+=dp[l][j][k][o4][o8][o];
//方程其實挺簡單的,就是看看這一位有沒有4/8/連續三位就好了
    LL ans=0;
    for(re int i=3;i<num;i++)//從位數小於給定數的開始
        for(re int j=1;j<=9;j++)
            for(re int k=0;k<=9;k++)
                ans+=dp[i][j][k][0][0][1]+dp[i][j][k][1][0][1]+dp[i][j][k][0][1][1];
    for(re int i=1;i<a[num];i++)//位數和給定數相等,但是首位比較小
        for(re int j=0;j<=9;j++)
            ans+=dp[num][i][j][0][0][1]+dp[num][i][j][1][0][1]+dp[num][i][j][0][1][1];
    int o4=0,o8=0,o=0;// 4/8/連續三位有沒有出現過
    if(a[num]==4) o4=1;
    if(a[num]==8) o8=1;
    for(re int l=num-1;l>=3;l--)//卡位,這裡保證從[l+1,num]和給定數是完全相等的
    {
        if(o4&&o8) break;
        for(re int i=0;i<a[l];i++)
        {
            int flag=o;
            if(i==a[l+1]&&a[l+1]==a[l+2]) o=1;//由於我們選擇這一位有可能會導致和上面的兩位重複,於是這裡需要判斷一下,如果有那麼就o=1接下來就算沒有選出連續三位也可以了
            for(re int j=0;j<=9;j++)
                {
                    int cnt=o;
                    if(i==j&&i==a[l+1]) o=1;
                    //和上面兩位重合的情況
                    if(o)
                    {
                        if(o4&&!o8) ans+=dp[l][i][j][1][0][1]+dp[l][i][j][0][0][1]
                                    +dp[l][i][j][0][0][0]+dp[l][i][j][1][0][0];
                        if(!o4)
                        {
                            if(!o8) ans+=dp[l][i][j][0][0][1]+dp[l][i][j][0][0][0]
                                        +dp[l][i][j][1][0][1]+dp[l][i][j][1][0][0]
                                        +dp[l][i][j][0][1][1]+dp[l][i][j][0][1][0];
                            if(o8) ans+=dp[l][i][j][0][1][1]+dp[l][i][j][0][1][0]
                                        +dp[l][i][j][0][0][1]+dp[l][i][j][0][0][0];
                        }
                    } 
                    if(!o)
                    {
                        if(o4&&!o8) ans+=dp[l][i][j][1][0][1]+dp[l][i][j][0][0][1];
                        if(!o4)
                        {
                            if(!o8) ans+=dp[l][i][j][0][0][1]+dp[l][i][j][1][0][1]+dp[l][i][j][0][1][1];
                            if(o8) ans+=dp[l][i][j][0][1][1]+dp[l][i][j][0][0][1];
                        }
                    }
                    o=cnt;
                }
                o=flag;//將o還原回來
        }
        o4=o4||(a[l]==4);
        o8=o8||(a[l]==8);
        o=o||((a[l]==a[l+1])&&(a[l+1]==a[l+2]));
        //判斷有沒有連續三位4/8出現過
    }
    if(o4&&o8) return ans;
    for(re int i=0;i<=9;i++)
        for(re int j=0;j<=9;j++)//卡最後兩位
            {
                if(i*10+j>a[2]*10+a[1]) continue;//不能超過給定數的最後兩位
                if(o4&&o8) continue;
                if(i==4||j==4)
                    if(o8) continue;//有4就不能有8
                if(i==8||j==8)
                    if(o4) continue;//有8就不能有4
                if(i==4&&j==8) continue;
                if(i==8&&j==4) continue;//更不可能同時出現
                if(o||(i==j&&j==a[3])||(i==a[3]&&a[3]==a[4])) ans++;
                //最後兩位仍有可能和前面構成三位連續
            }
    return ans;
}
int main()
{
    scanf("%lld%lld",&L,&R);
    printf("%lld\n",slove(R)-slove(L-1));
    return 0;
}