acdream 1064

题目链接:http://115.28.76.232/problem?pid=1064

题目大意:给定一个区间,找出在这个区间内,数位里有3或者有8但不能同时同时有3或8的数的个数

题目类型:数位dp

此题有两个版本的写法,时间复杂度一样,但是代码长度差很多

第一种是用dfs写的,代码如下:

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

int num[15];
int dp[15][3];


int dfs(int pos,int flag,int s)//pos表示考虑当前位,s表示之前的状态,flag表示当前位是否可以任意
{
	 if(pos == -1)			// 边界状态
	 	return s==1||s==2;	
	 if(flag && dp[pos][s] != -1) // 剪枝
	 	return dp[pos][s];
	 int x = flag ? 9 : num[pos];
	 int ans=0;
	 for(int i=0;i<=x;i++)		//递归找接下来的位
	 {
	 	int k=s;
	 	if(i==3)
	 		k=1|s;
	 	if(i==8)
	 		k=2|s;
	 	if(k<=2)			// 剪枝
	 		ans+=dfs(pos-1,flag||i<x,k);	 //dp[pos][s]表示当前考虑pos位,之前的状态为s,接下的(pos+1)个位的组合满足条件的个数
	 }
	 if(flag)
	 	dp[pos][s]=ans;
	 return ans;
}

int cal(int x)
{
	int k=0;
	while(x)
	{
		num[k++]=x%10;
		x/=10;
	}
	return dfs(k-1,0,0);
}


int main()
{
	int t;
	int l,r;
	scanf("%d",&t);
	memset(dp,-1,sizeof(dp));
	while(t--)
	{
		
		scanf("%d%d",&l,&r);
		printf("%d\n",cal(r)-cal(l-1));
	}
	return 0;
}

第二种写法是用循环写的,代码如下:

#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

long long dp[15][3];//分成三种情况,dp[i][0]表示都没有,dp[i][1]表示只有3,dp[i][2]表示只有8
int num[15];

int cal(int x)
{
    int l=1;
    while(x)//分离数字存入num数组
    {
        num[l++]=x%10;
        x/=10;
    }
    int flag3=0;
    int flag8=0;//标记前位3和8出现否
    long long ans=0;
    for(int i=l-1;i>0;i--)
    {
        if(!flag3 && !flag8)    //3和8都没出现过的时候
        {
            if(num[i]<=3)
            {
                ans+=num[i]*(dp[i-1][1]+dp[i-1][2]);//此情况只需考虑3和8单独存在的情况
                if(num[i]==3)
                    flag3=1;
            }
            else
            {
                if(num[i]>3&&num[i]<=8)
                {
                    ans+=(num[i]-1)*(dp[i-1][1]+dp[i-1][2]);//补其他数字的情况
                    ans+=dp[i-1][1]+dp[i-1][0];
                    if(num[i]==8)
                        flag8=1;
                }
                else
                {
                    ans+=(num[i]-2)*(dp[i-1][1]+dp[i-1][2]);//补3或8的情况
                    ans+=dp[i-1][1]+dp[i-1][2]+dp[i-1][0]*2;
                }
            }
        }
        else
        {
            if(flag3&&!flag8)
            {
                if(num[i]<=8)
                {    
                    ans+=num[i]*(dp[i-1][1]+dp[i-1][0]);
                    if(num[i]==8)
                        flag8=1;
                }
                else
                {
                    ans+=(num[i]-1)*(dp[i-1][1]+dp[i-1][0]);
                }

            }
            else
            {
                if(!flag3&&flag8)   //8已存在,不要考虑3的情况
                {
                    if(num[i]<=3)
                    {
                        ans+=num[i]*(dp[i-1][2]+dp[i-1][0]);//少了补3的情况
                        if(num[i]==3)
                            flag3=1;
                    }
                    else
                    {
                        ans+=(num[i]-1)*(dp[i-1][2]+dp[i-1][0]);
                    }
                }
            }
        }

    }
    return ans;
}

int main()
{
    dp[0][0]=1;
    dp[0][1]=dp[0][2]=0;
    for(int i=1;i<15;i++)
    {
        dp[i][0] = 8* dp[i-1][0];               //在没有3和8的基础上,再补其他的8个数
        dp[i][1] = dp[i-1][1] * 9 + dp[i-1][0];//在只有3的基础上补除8以外的数加上没有3和8的基础上补3
        dp[i][2] = dp[i-1][2] * 9 + dp[i-1][0];//在只有8的基础上补除3以外的数加上没有3和8的基础上补8
    }
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",cal(r+1)-cal(l));
    }
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值