Investigating Div-Sum Property UVA - 11361(数位DP讲解)

Investigating Div-Sum Property UVA - 11361(数位DP讲解)

  小白书上虽然说了一大堆,但并没有告诉我们这题的思路是运用数位DP,这是一类模板级别的题目了:给定范围[a,b],求满足g(x)的x的数目,g(x)往往都是关于数字x的每一数位上或者若干数位上的数字的对应关系。这样子的题目就可以使用数位DP来解决。大概的变形无出这种。我们的解决办法是让f(a)为[0,a]之间的满足g(x)的x的数目,同样有f(b)。所以之前的问题变成了求f(b)-f(a-1)。

  这样的题目,一般的a,b都会贼大,想要遍历[a,b]基本上超时无疑。求解f(x)的方法就涉及到了数位DP。比如说,我要求 f ( x ) , ( 0 ≤ x ≤ b ) f(x),(0\le x \le b) f(x),(0xb),那么任意 f(x)与若干 f ( y ) , ( 0 ≤ y < x ) f(y),(0 \le y < x) f(y),(0y<x),存在关系。那么就可以这么利用DP的思想。

  例如x=3212;

    f(3212)=f(***)+f(1***)+f(2***)+
    
    f(30**)+f(31**)+f(320*)+
    
    f(3210)+f(3211)+f(3212)

   ∗ * 号代表的是[0,9]内的任意数,这一串代表就将f(3212)集合划分成了不相交的若干个f(y)。但是只进行这一步并没有使得问题的规模变得更小。递归下去只会将所有的 f ( x ) , ( 0 ≤ x ≤ 3212 ) f(x),(0 \le x \le3212) f(x),(0x3212)都求一遍。网上大部分的讲解都只进行到了这一部分,然后就是说啥看代码…一头黑线,实际上节省遍历时间的地方不在这里,而是对这里对数位从高到低递归的过程中,有可能f(2***)的结果就是f(1***)的结果,使得递归过程直接跳过了f(2***)展开的子递归树,从而使得时间复杂度下降了。而这个巧合并不是瞎扯的,而是针对g(x)特点就是关于数字x的每一数位上或者若干数位上的数字的对应关系,才会使得数位DP的可行。这样子比O(n)遍历实际节省的时间取决于g(x)的特点。因此数位DP的速度可以很快很快。

  对于这道题的分析还没有结束,转移的状态除了主要的x之外还有两个,那就是余数 m 1 , m 2 m_1,m_2 m1,m2 m 1 m_1 m1是x的各数位的数字之和对k的余数, m 2 m_2 m2就是x对k的余数。f(****)就可以表示为f(4,0,0),4代表的是3212的最高位数,因为问题要求的就是两个余数都为0的情况的x的个数。而f(4,0,0),划分之后的 m 1 , m 2 m_1,m_2 m1,m2基本上不再会是0,0了。举例如:k=7,x=22***的解的个数是f(3,3,1),因为:

  1. 一共有三个*号。
  2. 2+2=4,剩下的3个未定数加起来要除以7余3的时候,全部加起来才会被7整除。
  3. 22000 mod 7 = 6,所以后三位数组成的整数mod 7 = 1时,才符合条件。

枚举的数字只是当前的最高位,从[0,9]之间:
f ( d , m 1 , m 2 ) = s u m ( f ( d − 1 , ( m 1 − x ) % k , ( m 2 − x ∗ 1 0 d − 1 ) % k ) ∣ x = 0 , 1 , 2... , 9 ) f(d,m_1,m_2)=sum(f(d-1,(m_1-x)\%k,(m_2-x*10^{d-1})\%k)|x=0,1,2...,9) f(d,m1,m2)=sum(f(d1,(m1x)%k,(m2x10d1)%k)x=0,1,2...,9)
  讲解到这里,其基本的数位DP思想已经到位了。就在于x若干个数位的数与x的某种对应关系使得减少计算成为可能。剩下的细节在于取模的时候要注意负数取模的问题。


这里记录一下传说中神牛的DP模板,对于上面提到题型基本上通吃,细节方面稍微注意改动就好了。

int dfs(int i, int s, bool e) {  
    if (i==-1) return s==target_s;  
    if (!e && f[i][s] != -1) 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;  
}  

佩服佩服,这代码之精简,浓缩了数位DP的思想精髓了。一看就懂了。下面是这道题的代码。

#include <iostream>
#include<cstdio>
#include <cstring>
#define Memset(x,a) memset(x,a,sizeof(x))
#define Debug(x) cout<<#x<<"  "<<x<<endl
#define FOR(i,a,b) for(int i=a;i<b;i++)
#define RFreopen freopen("in.txt","r",stdin)
#define WFreopen freopen("out.txt","w",stdout)
using namespace std;
int f[20][1000][1000],num[20];
int a,b,k;
int _1e(int x)
{
    int res = 1;
    FOR(i,0,x)
        res*=10;
    return res;
}

int dfs(int len,int m1,int m2,bool e)
{
    if(len==0) return (m1==0&&m2==0)?1:0;
    if(!e&&f[len][m1][m2]!=-1)  return f[len][m1][m2];
    int res=0,u=e?num[len]:9;
    FOR(i,0,u+1)
        res+=dfs(len-1,((m1-i)%k+k)%k,((m2-i*_1e(len-1))%k+k)%k,e&&u==i);
    return e?res:f[len][m1][m2]=res;
}
int solve(int a)
{
    Memset(num,0);
    int len=0;
    while(a)
    {
        num[++len] = a%10;
        a/=10;
    }
    return dfs(len,0,0,true);
}
int main()
{
    //RFreopen;
    //WFreopen;
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&a,&b,&k);
        Memset(f,-1);

        printf("%d\n",(k>100)?0:solve(b)-solve(a-1));
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值