下笔春蚕食叶声。

题解 UVA11361 【Investigating Div-Sum Property】

dfs实现数位DP

int dfs(int d,int m1,int m2,bool fl)

d:正在填从右往左第d位

m1表示数字和%k余数,m2表示该数%k余数

fl=1表示达到当前上限,0表示没限制(数位DP常规操作)……

我一开始是从最高位开始枚举的,但是出了些问题,现在这个写法是从最低位枚举。

之后自然就有dfs(d,m1,m2,fl)+=dfs(d-1,(m1+i)%k,(m2*10+i)%k,(i==num)&&fl)

部分详解参见代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int t,a,b,k,dp[35][110][110],c[35];//数组为什么那么小待会儿解释
int dfs(int d,int m1,int m2,bool fl){
    //正在填从右往左第d位,
    //m1表示数字和%k余数,m2表示该数%k余数
    //fl=1表示有限制,否则是没限制 
    //每次在尾巴上多加一位 
    if(d==0) return m1==0&&m2==0;//填完了 
    if(dp[d][m1][m2]!=-1&&!fl) return dp[d][m1][m2];//填过了,直接写答案 
    int num=9,ret=0;
    if(fl) num=c[d];//确认枚举的num,没限制就是9,有限制就是0 
    for(int i=0;i<=num;i++)
        ret+=dfs(d-1,(m1+i)%k,(m2*10+i)%k,(i==num)&&fl);
    //转移
    //没限制时:无论怎么填都是没限制
    //有限制:<num可以没限制了,=num还有限制
    //i==num&&fl 
	if(!fl) dp[d][m1][m2]=ret;
    return ret;
}
int solve(int x){
    memset(dp,-1,sizeof(dp));//清零
    int len=0;
    while(x){
    	c[++len]=x%10;
    	x/=10;
	}//分割数
    return dfs(len,0,0,1);
}
int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d",&a,&b,&k);
        if(k>=100) printf("0\n");
        //最多10位,数字和99,大于等于100的k肯定没有答案,解决了数组的问题
        else printf("%d\n",solve(b)-solve(a-1));
    }
    return 0;
}

重要提醒:

白书P114公式疑有错,而且和我的转移方法略有不同,但不论怎么说这个dp的设计还是非常妙的。

数据范围似乎是假的,如果你数组开的是dp[15][105][105]wa了,请改成dp[35][110][110]即可AC,把我给坑了

代码结构基本上是对的,但是极有可能发生和题解写的一模一样而WA或TLE的情况,这似乎是这题本身的问题,它的数据范围真的有毒。其中有一份代码洛谷TLE,在vjudgeWA,严重惊恐

应该没有什么其他问题了。Hzao的题解和书上有些地方相似,如果有问题可以看看那篇,ta纠正了书上公式的错误。

——ACwisher

posted @ 2020-06-11 18:28  ACwisher  阅读(140)  评论(0编辑  收藏  举报