HDU 4507 吉哥系列故事——恨7不成妻

题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=4507

一开始看到这题,数位dp,最初的想法是len为0的时候,只要符合要求,就返回之前组合成的数的平方,就像下面这样:

LL dfs(int len,int mod1,int mod2,bool flag,LL sum){
    if(len==0){
        if(mod1!=0&&mod2!=0) return (sum*sum)%MOD;
        return 0;
    }
    int mlen=flag?bit[len]:9;
    LL ret=0;
    for(int i=0;i<=mlen;++i){
        if(i==7) continue;
        ret=(ret+dfs(len-1,(i+mod1)%7,(mod2*10+i)%7,flag&&i==mlen,(sum*10+i)%MOD))%MOD;
        }
    return ret;
}


但这样太暴力了,很明显会tle,于是很简单就想到记忆化搜索,但是怎么记忆化搜索呢?单单加一个dp数组吗?读者不妨自己试下,试了你就知道会wa了,原因是因为一开始数位dp是从0开始的,因此我们dp用的数组(假设这个数组是这样定义的:dp[len][mod1][mod2])存的是前几位是0(len以前的数字都是0),长度为len的数字中满足条件的解。而根据题意,所求答案不仅仅和目前讨论的位数有关,还和前面已讨论的数字有关。所以单单返回之前计算出的数的平方和的思路是错误的。

所以接下来的问题是:用记忆化搜索的形式加速数位dp,但是记忆化搜索要记录什么信息呢?
因为记忆化搜索只能记录目前讨论的数位的信息,不可以和已讨论的数位有联系(这样的话每次数位dp的结果不仅仅和目前讨论数位有关,数组无法记录)。所以想到用结构体同时保存三个信息:目前讨论条件下,符合要求的数的个数cnt,符合要求的数的和sum,符合要求的数的平方和sqrsum。设base为目前讨论的数的前缀(就是下面代码里的f),ai为后面各个符合要求的后缀。有如下式子:

∑ai就是sum,∑ai^2就是sqrsum,于是代码就很好写了:
#include<cstdio>
#include<iostream>
#include<cstring>
typedef long long LL;
using namespace std;
const int MOD=1e9+7;
#define MS(x,y) memset(x,y,sizeof(x))
const int MAXN=40;
int bit[MAXN],len;
LL pow[MAXN];
struct Node{
	LL cnt,sum,sqrsum;
	bool vis;
}dp[MAXN][7][7];

Node dfs(int len,int mod1,int mod2,bool flag){
	if(len==0){
		if(mod1&&mod2) return (Node){ 1,0,0,0 };
		else return (Node){ 0,0,0,0 };
	}
	if(!flag&&dp[len][mod1][mod2].vis) return dp[len][mod1][mod2];
	int mlen=flag?bit[len]:9;
	Node ret=(Node){ 0,0,0,0 },nxt;
	for(int i=0;i<=mlen;++i){
		if(i==7) continue;
		nxt=dfs(len-1,(i+mod1)%7,(mod2*10+i)%7,flag&&i==mlen);
		LL f=i*pow[len-1]%MOD;
		ret.cnt=(ret.cnt+nxt.cnt)%MOD;
		ret.sum=(ret.sum+(nxt.sum+nxt.cnt*f)%MOD)%MOD;
		ret.sqrsum=(ret.sqrsum+((nxt.cnt*f%MOD*f%MOD+2*f*nxt.sum%MOD)%MOD+nxt.sqrsum)%MOD)%MOD;
	}
	if(!flag) ret.vis=true,dp[len][mod1][mod2]=ret;
	return ret;
}
LL solve(LL x){
	len=0;
	while(x) bit[++len]=x%10,x/=10;
	return dfs(len,0,0,true).sqrsum;
}

int main(){
	pow[0]=1;
	for(int i=1;i<MAXN;++i) pow[i]=10*pow[i-1];
	MS(dp,0);
	int T;
	scanf("%d",&T);
	while(T--){
		LL l,r;
		scanf("%I64d%I64d",&l,&r);
		printf("%I64d\n",((solve(r)-solve(l-1))%MOD+MOD)%MOD);
	}
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值