不要62 HDU - 2089(数位DP)

跟着最最厉害的老王学了一波
数位DP:求出在给定区间[A,B]内,符合条件p(i)的数的个数,条件p(i)一般与数的大小无关,而只与数的构成有关,A,B一般都很大,需要将区间处理成求解[x,B]-[x-(A-1)]d的个数(x根据问题确定,一般为0)
题意:传送门
题解:首先这个题可以根据打表得出答案

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=0x3f3f3f3f;
const int maxn=1e6+5;
inline int read()
{
    char ch=getchar();
    int f=1,x=0;
    while(!(ch>='0'&&ch<='9')){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
bool judge(int n)
{
    while(n){
        if(n%10==4)return false;
        if(n/10%10==6&&n%10==2)return false;
        n/=10;
    }
    return true;
}
int n,m,f[maxn];
int main()
{
    for(int i=0;i<maxn;i++){
        if(judge(i))f[i]=f[i-1]+1;
        else f[i]=f[i-1];
    }
    while(233){
        n=read();m=read();
        if(n==0&&m==0)break;
        printf("%d\n",f[m]-f[n-1]);
    }
    return 0;
}

考虑用数位DP来做,那么就先按照数位来遍历数字,但是这种却无法DP,引入数位DP中按数位遍历数字的方法,用到dfs,当固定前面的数位后,状态就可以传递了,也就是用字典树的思想来处理这个问题,实际上这个问题就是在字典树上进行搜索。

int dfs(int len,bool flag)
{
    if(len==-1)return 1;
    int end=flag?p[len]:9;
    int ans=0;
    for(int i=0;i<=end;i++){
        ans+=dfs(len-1,flag&&i==end);
    }
    return ans;
}
int solve(int n)
{
    int len=0;
    while(n){
        p[len++]=n%10;
        n/=10;
    }
    return dfs(len-1,true);
}

把老王的图附上:
字典树
然后在这个字典树上进行统计出符合条件的数即可。

int dfs(int len,bool six,bool flag)
{
    if(len==-1)return 1;
    int end=flag?p[len]:9;
    int ans=0;
    for(int i=0;i<=end;i++){
        if(i==4||(six&&i==2))continue;
        ans+=dfs(len-1,i==6,flag&&i==end);
    }
    return ans;
}

这样的话这个题虽然不会T,但是实际上却是非常耗时的,然后考虑使用记忆化来进行优化,首先进行状态压缩,用dp[len][2],用这个数组来保存在len长度下,且上一位是不是6的状态的答案个数(还是蛮不好想的)。当flag为true时不用记忆化,是因为在边界上,没有搜索到全部情况,记忆化搜索以后,不仅解决了子问题重叠的问题,多次查询,还大大减少了求解的状态数O(10*2),对于非常大的数,根据题目进行状态压缩以后,需要求解的状态数也不会很大。
最终代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int INF=0x3f3f3f3f;
inline int read()
{
    char ch=getchar();
    int f=1,x=0;
    while(!(ch>='0'&&ch<='9')){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
int n,m,p[10],dp[10][2];
int dfs(int len,bool six,bool flag)
{
    if(len==-1)return 1;
    if(!flag&&dp[len][six])return dp[len][six];
    int end=flag?p[len]:9;
    int ans=0;
    for(int i=0;i<=end;i++){
        if(i==4||(six&&i==2))continue;
        ans+=dfs(len-1,i==6,flag&&i==end);
    }
    if(!flag)dp[len][six]=ans;
    return ans;
}
int solve(int n)
{
    int len=0;
    while(n){
        p[len++]=n%10;
        n/=10;
    }
    return dfs(len-1,false,true);
}
int main()
{
    while(233){
        n=read();m=read();
        if(n==0&&m==0)break;
        printf("%d\n",solve(m)-solve(n-1));
    }
    return 0;
}

总结我现在还不能说这话:上面就是数位DP的基本思想,之后对于不同的题,只需要根据题目改变下状态,改变下状态数组和dfs即可。
数位DP还可能搭配其他算法AC自动机求解问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值