模板 - 动态规划 - 数位dp

#include<bits/stdc++.h>
using namespace std;
#define ll long long

int a[20];
ll dp[20][20/*可能需要的状态1*/][20/*可能需要的状态2*/];//不同题目状态不同
ll dfs(int pos,int state1/*可能需要的状态1*/,int state2/*可能需要的状态2*/,bool lead/*这一位的前面是否为零*/,bool limit/*这一位是否取值被限制(也就是上一位没有解除限制)*/)
//不是每个题都要处理前导零
{
    //递归边界,最低位是0,那么pos==-1说明这个数枚举完了
    if(pos==-1)
        return 1;/*这里返回1,表示枚举的这个数是合法的,那么这里就需要在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。 */
    //第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
    if(!limit && !lead && dp[pos][state1][state2]!=-1)
        return dp[pos][state1][state2];
    /*常规写法都是在没有限制的条件记忆化,这里与下面记录状态对应*/
    int up=limit?a[pos]:9;//根据limit判断枚举的上界up
    ll ans=0;
    //开始计数
    for(int i=0;i<=up;i++)//枚举,然后把不同情况的个数加到ans就可以了
    {
        int new_state1=???;
        int new_state2=???;
        /*
        计数的时候用continue跳过不合法的状态,不再搜索
        */

        //合法的状态向下搜索
        ans+=dfs(pos-1,new_state1,new_state2,lead && i==0,limit && i==a[pos]);//最后两个变量传参都是这样写的
    }
    //计算完,记录状态
    if(!limit && !lead)
        dp[pos][state1][new_state2]=ans;
    /*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
    return ans;
}

ll solve(ll x)
{
    //可能需要特殊处理0或者-1
    if(x<=0)
        return ???;

    int pos=0;
    while(x)//把数位分解
    {
        a[pos++]=x%10;//编号为[0,pos),注意数位边界
        x/=10;
    }

    return dfs(pos-1/*从最高位开始枚举*/,0/*可能需要的状态1*/,0/*可能需要的状态2*/,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
}

int main()
{
    memset(dp,-1,sizeof(dp));
    //一定要初始化为-1

    ll le,ri;
    while(~scanf("%lld%lld",&le,&ri))
    {
        printf("%lld\n",solve(ri)-solve(le-1));
    }
}

猜你喜欢

转载自www.cnblogs.com/Yinku/p/10416210.html