研究问题:区间【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状态