zoukankan      html  css  js  c++  java
  • 【[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
    ",slove(R)-slove(L-1));
        return 0;
    }
    
    
  • 相关阅读:
    Android license status unknown,亲测有效
    android studio 如何升级sdk
    Java就业前景如何?前途&钱途?如何成为Java工程师?
    Java程序的编写与执行、Java新手常见的问题解决
    学习java需要掌握什么基础?如何学
    Java8中你可能不知道的一些地方之Optional实战
    P1407 [国家集训队]稳定婚姻
    P5960 【模板】差分约束算法
    P3388 【模板】割点(割顶)
    2020.8.4
  • 原文地址:https://www.cnblogs.com/asuldb/p/10206188.html
Copyright © 2011-2022 走看看