HDU-3652 B-number (数位dp)

题目:

http://acm.hdu.edu.cn/showproblem.php?pid=3652

题意:

计算区间内是13的倍数并且包含13的数的个数。

思路:

数位dp,既要包含13又要是13的倍数,所以有2个状态。具体见代码注释。

代码:

预处理写法:

//kopyh
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define MOD 1000000007
#define EPS 1e-6
#define N 21
using namespace std;
int n,m,sum,res,flag;
//dp[i][j][k]第i位余数为j的是否包含13的数的个数,k==0不包含,k==1首位为3,k==2包含13
//a[i] 1ei的余数
int dp[N][13][3],a[N];
void init()
{
	//计算每个位数的余数
    a[0]=1;
    for(int i=1;i<N;i++)
        a[i] = a[i-1]*10%13;

    memset(dp,0,sizeof(dp));
    dp[0][0][0] = 1;
    for(int i=1;i<N;i++)//迭代位数
        for(int j=0;j<13;j++)//枚举余数
        {
            for(int k=0;k<10;k++)//枚举首位数字
            {
				dp[i][(j+a[i-1]*k)%13][0]+=dp[i-1][j][0];
				dp[i][(j+a[i-1]*k)%13][2]+=dp[i-1][j][2];
			}
			//记录首位为3的个数
			dp[i][(j+a[i-1]*3)%13][1] += dp[i-1][j][0];
			//把新包含13的个数调整到dp[i][][2]中
            dp[i][(j+a[i-1])%13][0] -= dp[i-1][j][1];
            dp[i][(j+a[i-1])%13][2] += dp[i-1][j][1];
        }
}
int solve(int x)
{
	//取出数位
    int dig[N],len=0;
    while(x)dig[len++]=x%10,x/=10;
    dig[len]=0;
    int flag=0,ans=0,mod=0;
	//从高位到低位枚举每一位数
    for(int i=len-1;i>=0;i--)
    {
//枚举当前位为j,得到前面的余数为(mod+j*a[i])%13,
//后面位数中余数是(13-(mod+j*a[i])%13)%13的个数都是答案。
        for(int j=0;j<dig[i];j++)
            ans+=dp[i][(13-(mod+j*a[i])%13)%13][2];
        if(flag)
        {
//前面已经有了13,只要是前面和后面所有位数的余数为0的就是答案。
            for(int j=0;j<dig[i];j++)
                ans+=dp[i][(13-(mod+j*a[i])%13)%13][0];
        }
        else
        {
			//首位是3的余数和为0就是答案。
            if(dig[i+1]==1&&dig[i]>3)
                ans+=dp[i+1][(13-mod)%13][1];
			//取当前位为1后面首位为3的是答案。
            if(dig[i]>1)
                ans+=dp[i][(13-(mod+a[i])%13)%13][1];
        }
		//已经出现过13,标记。
        if(dig[i+1]==1&&dig[i]==3)flag=1;
        mod=(mod+dig[i]*a[i])%13;
    }
    return ans;
}
int main()
{
    int i,j,k,kk,cas,T,t,x,y,z;
    init();
    while(scanf("%d",&n)!=EOF)
        printf("%d\n",solve(n+1));
    return 0;
}


深搜写法:

//kopyh
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
#define MOD 1000000007
#define EPS 1e-6
#define N 11
using namespace std;
int n,m,sum,res,flag;
int dp[N][13][3],dig[N];
//len:当前位,mod:前面留下的余数大小,k:前面是否出现13,flag:前面每一位是否都是上限。
int dfs(int len, int mod, int k, int flag)
{
    if(len<=0)return (!mod && k==2);
    //通过dp优化
    if(!flag && dp[len][mod][k]!=-1)return dp[len][mod][k];
    //通过是否为上限确定当前位可取的最大值
    int num = flag?dig[len]:9;
    int ans=0;
    for(int i=0;i<=num;i++)
    {
        int modt = (mod*10+i)%13;
        //确定是否已出现13
        int kt;
        if(k==2 || k==1&&i==3)kt=2;
        else if(i==1)kt=1;
        else kt=0;
        
        ans+=dfs(len-1,modt,kt,flag&&num==i);
    }
    if(!flag)dp[len][mod][k] = ans;
    return ans;
}
int main()
{
    int i,j,k,kk,cas,T,t,x,y,z;
    while(scanf("%d",&n)!=EOF)
    {
        memset(dp,-1,sizeof(dp));
        sum=0;
        while(n)dig[++sum]=n%10,n/=10;
        printf("%d\n",dfs(sum,0,0,1));
    }
    return 0;
}













评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值