【数位DP】BZOJ 3679: 数字之积

 BZOJ 3679: 数字之积

题意:求[L, R)区间内,各个数位之积在(0, n]的数的个数。

思路:因为L,R的范围有1e18,如果每个数位都是9,9^18是最大的乘积了吧www……是不可能开的下数组的。那怎么办,我们怎么处理乘积呢?首先说,n的范围是<=1e9,所以一旦大于1e9我们可以直接return 0. 那么问题就变成了怎么处理1e9之内的乘积。(还是很大啊qaq)。

可(大)以(佬)发(们)现(说),乘积只有2、3、5、7这四个质因子,而这四个质因子在1e9内的乘积种类数是有限的,打表发现一共有:

所以我们就只需要离散化超级无敌大乘积即可!

这里我们有两种方法:(这里我用的map映射)

  1. 预处理:将1e9之内的乘积打出来并且存起来。

  2. 动态存乘积。跑dfs的时候,如果该乘积没有出现过(mp[mul] == 0),那么我们就将它存起来。

  • 说到这个mp[mul] == 0,我真的哭辽。第一种方法,预处理的时候,最开始我mp的映射是从0开始的,也就是mp[1] = 0. 但是如果乘积为0那对应的也是0www,所以就造成了WAWAWAWA

  • 第二种方法,那肯定也不能从0开始,因为不存在的乘积为0的嘛

  • debug的时候还以为是预处理写错了,【其实最开始真的写错了】qaq,后来看了这个题——丑数,就是找只有因子2、3、5、7的数,然后才改对。我真的好菜。这个题又用了一下午www

  • 最后对自己说一句Fighting!!!!

 

 

【还有最最开始的错误做法】我是直接记录乘积是不是满足条件的状态:sta == 0: 乘积无意义;sta == 1: 乘积为0;sta == 2: 乘积<=n;sta == 3: 乘积 > n.   但是这个显然就错了叭。因为相同的sta的可能乘积并不相同,也就是说并不具有无后效性。可能跑完这个数位就更新了dp[pos][sta],但是其他的乘积的时候可能是相同的状态,但是却直接返回了之前有的。所以错!

 

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define INF 0x3f3f3f3f
#define lowbit(x) x & (-x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxN = 5200 + 5;
const ll pri[4] = {2, 3, 5, 7};
ll n, L, R;
int a[20];
ll dp[20][maxN];
int cnt;
map<ll, int>mp;
void pre()
{
    cnt = 1;
    set<ll>st;
    st.insert(1);
    while(!st.empty())
    {
        ll tmp = *st.begin();
        if(tmp > 1e9)
        {
            //printf("%d\n", mp.size());
            return;
        }
        mp[tmp] = cnt ++;
        st.erase(tmp);
        for(int i = 0; i < 4; i ++ )
            st.insert(tmp * pri[i]);
    }
}
ll dfs(int pos, ll mul, bool lead, bool limit)
{
    if(mul > n)
        return 0;
//    if(mp[mul] == 0)
//        mp[mul] = cnt ++;
    if(pos == -1)
        return mul <= n && mul > 0;
    if(!limit && !lead && dp[pos][mp[mul]] != -1)
        return dp[pos][mp[mul]];
    int up = limit ? a[pos] : 9;
    ll ans = 0;
    for(int i = 0; i <= up; i ++ )
    {
        ll nextMul;
        if(!lead)
            nextMul = mul * i;
        else if(i != 0)
            nextMul = i;
        else
            nextMul = 0;
        ans += dfs(pos - 1, nextMul, lead && i == 0, limit && i == a[pos]);
    }
    if(!limit && !lead)
        dp[pos][mp[mul]] = ans;
    return ans;
}

ll solve(ll x)
{
    int pos = 0;
    while(x)
    {
        a[pos ++ ] = x % 10;
        x /= 10;
    }
    return dfs(pos - 1, 0, true, true);
}
int main()
{
    pre();
//    cnt = 1;
    while(~scanf("%lld", &n))
    {//这里的dp初始化不能放在外边,因为dp值是随着n的变化而变化的。
        memset(dp, -1, sizeof(dp));//虽说这个题是单组输入吧……
        scanf("%lld%lld",&L, &R);
        printf("%lld\n", solve(R - 1) - solve(L - 1));
    }
    return 0;
}
/*
1 1 1000000000000000000
 ans = 18
 */

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值