HDU 3709 ZJU 3416 Balanced Number 数位dp 记忆搜索

本文介绍了一种使用记忆搜索算法来计算特定数值范围内平衡数的数量的方法。通过递归地分解数字并应用预计算结果避免重复计算,有效地解决了问题。特别关注了边界条件的处理以及在不同平台上代码实现细节的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:http://www.cppblog.com/Yuan/archive/2013/03/09/139299.html#198320


用这种方法写,一个流程是,列出式子(pre*10^pos + next)  pre是确定的,next是变量
      所以参数就为pre,pos加上一些其他的,还有一个标记doing,表示是计算有上界限制的还是没有上界限制(所有情况,即end=9)  
      dfs(pos-1 , npre , doing && i ==end)

平衡,即∑a[i]*(i-o) = 0   o为支点
对于一个数,支点o是唯一的,所以不会有重复计数的情况(但有一种特殊的,就是0,00,000等都是一样的,会计算多次,
最后减去即可)
假设检查到pos处,对于上面的式子∑a[i]*(i-o) = 0,这里确定了支点为o
之前的数其∑a[i]*(i-o)的结果为pre
所以参数需要为pos , o , pre

当检查到pos=-1时,return pre == 0
否则,看当前是计算所有情况还是具体情况(doing为1表示所有情况,为0表示特殊情况,就是有个位置受到限制)
如果是所有情况且dp值!=-1,直接return
否则就枚举0到end

而支点o需要在最外层枚举出来

 

记忆搜索:   用这种方法写,一个流程是,列出式子(pre*10^pos + next)  pre是确定的,next是变量
                    所以参数就为pre,pos加上一些其他的,还有一个标记doing,表示是计算有上界限制的还是没有上界限制(所有情况,即end=9)  
                    dfs(pos-1 , npre , doing && i ==end)

ZJU AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

long long dp[19][19][2000];
int digit[19];

long long dfs(int pos , int o , int pre , bool doing)
{
	if(pos == -1)
		return pre == 0; //pos等于-1 说明已经计算到个位。此时pre就是其值,等于说明是个平衡数
	if(pre < 0)
		return 0; //剪枝

	if(!doing && dp[pos][o][pre] != -1)
		return dp[pos][o][pre];

	long long ans = 0;
	int end = doing ? digit[pos] : 9;
	for(int i = 0 ; i <= end ; i ++)
	{
		int npre = pre;
		npre += (pos-o)*i;
		ans += dfs(pos-1 , o , npre , doing && i == end); //每位的最高位处doing为false,导致其衍生的都为false
	}

	if(!doing)
		dp[pos][o][pre] = ans;
	return ans;
}

long long cal(long long x)
{
	int pos = 0;
	while(x)
	{
		digit[pos++] = x % 10;
		x /= 10;         
	}
	long long ans = 0;
	for(int o = 0 ; o < pos ; o ++)
	{
		ans += dfs(pos-1 , o , 0 , 1);
	}

	return  ans - (pos-1);//duplicate 0
}

int main()
{
	int T;
	for(scanf("%d",&T) ; T--; )
	{
		long long left , right;
		memset(dp,-1,sizeof(dp));
		scanf("%lld%lld",&left , &right);
		printf("%lld\n",cal(right) - cal(left - 1));
	}
	return 0;
}

 

到hdu上提交,半天都是wa, 逐字逐句检查,检查边界,搞了半天。最后奇葩的是,需要将%ldd 改成 %I64d !!! 改完了之后,在hdu上ac, 在zju上开始wa了。。

 

hdu ac 代码:

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

long long dp[19][19][2000];
int digit[19];

long long dfs(int pos , int o , int pre , bool doing)
{
    if(pos == -1)
        return pre == 0; //pos等于-1 说明已经计算到个位。此时pre就是其值,等于说明是个平衡数
    if(pre < 0)
        return 0;

    if(!doing && dp[pos][o][pre] != -1)
        return dp[pos][o][pre];

    long long ans = 0;
    int end = doing ? digit[pos] : 9;
    for(int i = 0 ; i <= end ; i ++)
    {
        int npre = pre;
        npre += (pos-o)*i;
        ans += dfs(pos-1 , o , npre , doing && i == end); //每位的最高位处doing为false,导致其衍生的都为false
    }

    if(!doing)
        dp[pos][o][pre] = ans;
    return ans;
}

long long cal(long long x)
{
    int pos = 0;
    while(x)
    {
        digit[pos++] = x % 10;
        x /= 10;         
    }
    long long ans = 0;
    for(int o = 0 ; o < pos ; o ++)
    {
        ans += dfs(pos-1 , o , 0 , 1);
    }

    return  ans - (pos-1);//duplicate 0
}

int main()
{
    int T;
    for(scanf("%d",&T) ; T--; )
    {
        memset(dp,-1,sizeof(dp));
        long long left , right;
        scanf("%I64d%I64d",&left , &right);
        printf("%I64d\n",cal(right) - cal(left - 1));
    }
    return 0;
}


 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值