zoukankan      html  css  js  c++  java
  • 模板

    #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][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
    ",solve(ri)-solve(le-1));
        }
    }

    其实另一种计数写法对别的题目有一定的启发性,需要特别注意的是,无论哪种写法的dp结果中存的数字都是和le与ri无关的。所以在数位受限时不能取用计算过的dp值,也不能更新dp值,不受限的情况可以重复利用。

    无注释版:

    #include<bits/stdc++.h>
    using namespace std;
    #define ll long long
    
    int a[20];
    ll dp[20][MAXS1][MAXS2];
    ll dfs(int pos,int s1,int s2,bool lead,bool limit) {
        if(pos==-1) {
            return ?;
        }
        if(!limit && !lead && dp[pos][s1][s2]!=-1)
            return dp[pos][s1][s2];
        int up=limit?a[pos]:9;
        ll ans=0;
        for(int i=0; i<=up; i++) {
            int ns1=op1(s1);
            int ns2=op2(s2);
            ans+=dfs(pos-1,ns1,ns2,lead && i==0,limit && i==a[pos]);
        }
        if(!limit && !lead)
            dp[pos][s1][s2]=ans;
        return ans;
    }
    
    ll solve(ll x) {
        if(x<=0)
            return ?;
    
        int pos=0;
        while(x) {
            a[pos++]=x%10;
            x/=10;
        }
    
        return dfs(pos-1,INITS1,INITS2,true,true);
    }
    
    int main() {
        memset(dp,-1,sizeof(dp));
    
        ll le,ri;
        while(~scanf("%lld%lld",&le,&ri)) {
            printf("%lld
    ",solve(ri)-solve(le-1));
        }
    }

    一个更简单的模板,去掉了很多奇奇怪怪的东西,比如前导0,前导0的确应该特殊考虑而不能一概而论。

    int dfs(int i, int s, bool e) {
        if (i==-1) return s==target_s;
        if (!e && ~f[i][s]) return f[i][s];
        int res = 0;
        int u = e?num[i]:9;
        for (int d = first?1:0; d <= u; ++d)
            res += dfs(i-1, new_s(s, d), e&&d==u);
        return e?res:f[i][s]=res;
    }

    看起来清爽多了,其中:

    f为记忆化数组;

    i为当前处理串的第i位(权重表示法,也即后面剩下i+1位待填数);

    s为之前数字的状态(如果要求后面的数满足什么状态,也可以再记一个目标状态t之类,for的时候枚举下t);

    e表示之前的数是否是上界的前缀(即后面的数能否任意填)。

    for循环枚举数字时,要注意是否能枚举0,以及0对于状态的影响,有的题目前导0和中间的0是等价的,但有的不是,对于后者可以在dfs时再加一个状态变量z,表示前面是否全部是前导0,也可以看是否是首位,然后外面统计时候枚举一下位数。

    注意:

    不满足区间减法性质的话,不能用solve(r)-solve(l-1)。


    看了学长的部分博客之后发现其实使用f[i][j][st]表示以j开头的i位数满足条件st的数的个数也是可以的。待更新。

  • 相关阅读:
    LCD编程_显示文字
    LCD编程_画点线圆
    LCD编程_简单测试
    LCD编程框架组织
    LCD编程_LCD控制器
    编程——抽象出重要的结构体
    LCD裸板编程_框架
    S3C2440_LCD控制器
    关于加密与解密的问题。
    [13期]mysql-root全手工注入写马实例实战
  • 原文地址:https://www.cnblogs.com/Yinku/p/10416210.html
Copyright © 2011-2022 走看看