acm专题训练15——数位dp

研究问题:区间【l,r】内满足某条件的数字个数
以专题训练的第一题即hdoj3555为例
求区间【l,r】内带有”49“的数的数量
题目链接

初始化:

memset(dp,-1,sizeof(dp));

dp[pos][sta]保存的是当pos位不受限制的情况下,状态sta的值

为什么这里要加一个pos位不受限制?

以250为例
其中的00-99的状态是可以通dp[十位][sta]保存
0-9的状态可以通过dp[个位][sta]保存
在下次计算一个数,例如105时,
其中00-99的状态可以直接拿过来用,
只要再计算100-105中符合条件的数即可

memset可以放在多组读入外面
因为一个个数是否满足题目要求与输入无关!

步骤1:按位存数

ll solve(ll x)
{
    ll pos=0;
    while(x) a[++pos]=x%10,x/=10;
    return dfs(pos,0,true);
}

int dfs(int pos,int sta,bool limit)
pos:当前处理的位数
sta:状态
limit:当前位数是否受限制
受限:以250为例,最高位(百位)受到限制只能为012,当百位为2时,十位受到限制,只能为01234,当十位为5时,个位受到限制,只能为0
如果左边一位受限,这一位必然受限,
如果左边一位不受限,这一位必不受限

solve()将一个数的每位存入数组中,
每一位上的值即为这一位的上界
从这个数最高位开始搜

步骤2:dfs

ll dfs(int pos,int sta,bool limit)
{
    int nowsta;
    if(pos==0) return sta==2;
    if(!limit&&dp[pos][sta]!=-1) return dp[pos][sta];
    int up=limit?a[pos]:9;
    ll tmp=0;
    for(int i=0;i<=up;i++)
    {
        nowsta=sta;
        if(sta==0&&i==4) nowsta=1;
        if(sta==1&&i!=4) nowsta=0;
        if(sta==1&&i==9) nowsta=2;
        tmp+=dfs(pos-1,nowsta,limit&&i==a[pos]);
    }
    if(!limit) dp[pos][sta]=tmp;
    return tmp;
}

这边开始正式讲算法,
核心思想:状态转移
判断这个数中是否含49
可以分为三种状态
status0:到目前这一位为止,并未出现49或者4
status1:到目前这一位为止,并未出现49,但上一位为4
status2:到目前这一位为止,已出现过49

(1)判断完了吗?

如果pos为0,即判断完了,
如果status为2,那么这个数是ok的

(2)记忆化dfs

如果这一位没有受限制并且之前已经计算过dp[pos][sta],直接返回这个值

(3)状态转移

之前是状态2的以后都一直是状态2了
之前是状态1的,如果这一位为9,转移到状态2
之前是状态0的,如果这一位为4,转移到状态1
然后搜索下一位递归求解

(4)保存状态

如果没有受限,将目前状态的值保存下来

题解

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<long long,long long> pll;
template<class T>inline void rd(T &x){x=0;char o,f=1;while(o=getchar(),o<48)if(o==45)f=-f;do x=(x<<3)+(x<<1)+(o^48);while(o=getchar(),o>47);x*=f;}
ll a[20],dp[20][3];
ll dfs(int pos,int sta,bool limit)
{
    int nowsta;
    if(pos==0) return sta==2;
    if(!limit&&dp[pos][sta]!=-1) return dp[pos][sta];
    int up=limit?a[pos]:9;
    ll tmp=0;
    for(int i=0;i<=up;i++)
    {
        nowsta=sta;
        if(sta==0&&i==4) nowsta=1;
        if(sta==1&&i!=4) nowsta=0;
        if(sta==1&&i==9) nowsta=2;
        tmp+=dfs(pos-1,nowsta,limit&&i==a[pos]);
    }
    if(!limit) dp[pos][sta]=tmp;
    return tmp;
}
ll solve(ll x)
{
    ll pos=0;
    while(x) a[++pos]=x%10,x/=10;
    return dfs(pos,0,true);
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("hzh.in","r",stdin);
    //freopen("hzh.out","w",stdout);
    #endif
	memset(dp,-1,sizeof(dp));
	ll t,n;
	rd(t);
	while(t--)
    {
		rd(n);
    	cout<<solve(n)<<endl;
	}
    return 0;
}

多状态转移

以专题训练第二题1002(hdoj3652)为例
题目链接

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<long long,long long> pll;
template<class T>inline void rd(T &x){x=0;char o,f=1;while(o=getchar(),o<48)if(o==45)f=-f;do x=(x<<3)+(x<<1)+(o^48);while(o=getchar(),o>47);x*=f;}
ll a[20],dp[20][15][3];
ll dfs(int pos,int sta1,int sta2,bool limit)
{
    int nowsta1,nowsta2;
    if(pos==0) return sta1==0&&sta2==2;
    if(!limit&&dp[pos][sta1][sta2]!=-1)
        return dp[pos][sta1][sta2];
    int up=limit?a[pos]:9;
    ll tmp=0;
    for(int i=0;i<=up;i++)
    {
        nowsta1=(sta1*10+i)%13;
        nowsta2=sta2;
        if(sta2==0&&i==1) nowsta2=1;
        if(sta2==1&&i!=1) nowsta2=0;
        if(sta2==1&&i==3) nowsta2=2;
        tmp+=dfs(pos-1,nowsta1,nowsta2,limit&&i==a[pos]);
    }
    if(!limit) dp[pos][sta1][sta2]=tmp;
    return tmp;
}
ll solve(ll x)
{
    ll pos=0;
    while(x) a[++pos]=x%10,x/=10;
    return dfs(pos,0,0,true);
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("hzh.in","r",stdin);
    //freopen("hzh.out","w",stdout);
    #endif
    memset(dp,-1,sizeof(dp));
    ll n;
    while(~scanf("%lld",&n))
        cout<<solve(n)<<endl;
    return 0;
}

多增加一维保存第二个状态即可
这题里是保存了一个mod状态

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值