zoukankan      html  css  js  c++  java
  • 构造+数位dp

    参考博客:

    题目链接:

    题意:给定正整数a,b,k,你的任务是在所有满足a<=n<=b中的整数n中,统计有多少个满足n自身是k的倍数,且n的各位数字之和也是k的倍数。

    【思路】

       这种题的固定套路是设f(x)为[0,x]中满足题意的解的个数,那么本题的答案就是f(b)-f(a-1)。关键问题就是求解f函数。因为数据范围太大,无法穷举,所以这道题要用分段求和的思想来解决问题。比如说要求解f(3212),那么就把[0,3212]拆分出来,也就是

    第一部分 0***,1***,2****

    第二部分 30**,31**

    第三部分 320*

    第四部分 3210,3211,3212

    (*表示0-9任意一个数)

    然后分别求解求和,求解时用到数位dp,设dp(d,m1, m2)表示有d位*,各位数字和%k==m1,整体%k==m2,那么现在考虑它的递推式。假设d位*中的最高位是x,那么现在的数字就是x****…*(d-1个*),设后面d-1个*的各位数字之和为a,整体为b,那么(x+a)%k=m1, (x*10^(d-1)+b)%k=m2

    反解出a%k=((m1-x)%k+k)%k, b%k=((m2-10^(d-1)*x)%k+k)%k

    所以递推公式就是dp(d,m1, m2)=sum{dp(d-1, a%k, b%k)} (0<=x<=9)

    对递归结束条件d==0时的解有两种理解方法

    <1>当d==1时,即dp(1,m1,m2)将d==1代入可得a%k=((m1-x)%k+k)%k, b%k=((m2-x)%k+k)%k。而对dp(1,m1,m2)来说,只有一个*,位数和和自身相等,如果m1!=m2,那么dp(1,m1,m2)=0,如果m1==m2,那么当x%k==m1时即x=ck+m1即(m1-x)%k=0即a%k=0,b%k=0时有一组对应解。所以dp(0,m1,m2)==1(m1=0, m2=0),其他情况dp(0,m1, m2)==0。

    <2>其实直观的去想,当d==0时一个*也没有,表示的就是数字0,而0对任何数求余还是0,所以当m1==m2==0的时候,0符合条件dp(0,m1,m2)==1,否则为0。

    还有一个细节要注意就是虽然k的范围是[1,10000]但是由于a,b都在int范围内,所以位数和最大不会超过82,当k>82以后,无论如何一个int整数的各位数字之和 mod k != 0,也就是无解,直接输出0即可。 

    最后就是f(x)的计算了,把x的每一位都存储起来,低位在前。通过模拟的方式,用bitsum记录之前已经积累的位数和,sum记录之前已经累计的整体和,具体的细节还要在代码中体现。

    #include<bits/stdc++.h>
    using namespace std;
     
    const int pw[15] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
     
    int a, b, k;
    int dp[15][100][100];
    int bit[15];
     
    int dfs(int d, int m1, int m2) {
        if (0 == d)    return dp[d][m1][m2] = (m1 == 0 && m2 == 0) ? 1 : 0;//关键! 
        if (-1 != dp[d][m1][m2]) return dp[d][m1][m2];
        
        int ans = 0;
        for (int x = 0; x <= 9; x++) {
            ans += dfs(d - 1, ((m1 - x) % k + k) % k, ((m2 - x*pw[d-1]) % k + k) % k);
        }
        return dp[d][m1][m2] = ans;
    }
     
    int f(int x) {//求出[0,x]中符合题意的数的个数     
     
        if (0 == x) return 1;//特例,0对任何数求余都是0 
        
        //按位存储数字 
        int cpy = x, size = 0;
        memset(bit, 0, sizeof(bit));
        while (cpy) {
            bit[size++] = cpy % 10;
            cpy /= 10;
        }
        
        int ans = 0;
        int bitsum = 0;//位数和 
        int sum = 0;//整体和 
        
        for (int i = size - 1; i >= 0; i--) {
            if (i) {
                 for (int j = 0; j < bit[i]; j++) {
                    ans += dfs(i, (k-(bitsum+j)%k )%k, (k- (sum+j*pw[i])%k )%k );
                    //ans += dfs(i, ((-(bitsum+j))%k + k)%k  ,  ((-(sum+j*pw[i]))%k + k)%k  );
                    //两种写法,同模的式子正确即可 
                }
            }
            else {
                for (int j = 0; j <= bit[i]; j++) {//个位,可以取到最大值 
                    ans += dfs(i, (k-(bitsum+j)%k )%k, (k- (sum+j*pw[i])%k )%k );
                }
            }
            bitsum += bit[i];//及时更新 
            sum += bit[i]*pw[i];
        }
        
        return ans;
    }
     
    int main() {
        int t;
        scanf("%d", &t);
        while (t--) {
            scanf("%d%d%d", &a, &b, &k);
            if (k > 90) { printf("0
    "); }
            else {
                memset(dp, -1, sizeof(dp));
                int ans = f(b) - f(a - 1);
                printf("%d
    ", ans);
            }
        }
        return 0;
    }
    View Code
  • 相关阅读:
    高效并发服务器模型
    Linux下Wiki服务器的搭建
    Wiki程序PmWiki的安装和汉化
    Linux 套接字编程中的 5 个隐患
    IOCP简介
    IP协议详解之IP地址要领
    IP协议详解之配套协议:ARP, ICMP
    超级详细Tcpdump 的用法
    如何测试主机的MTU多大?
    Linux下Socket编程的端口问题( Bind error: Address already in use )
  • 原文地址:https://www.cnblogs.com/linhaitai/p/9783815.html
Copyright © 2011-2022 走看看