HDU 3652 数位DP

本文介绍了一种使用数位动态规划(DP)的方法来解决一类特殊问题:找出1到n范围内既能被13整除且含有子串“13”的所有数字。通过定义状态dp[i][j][k][m],该算法有效地遍历并计算所有符合条件的数字。
摘要由CSDN通过智能技术生成

题目大意:给一个数字n,找出1-n中,有多少个数字,既能被13整除,又包含子串“13”。

很裸的数位DP了,

dp[ i ][ j ][ k ][ m ]:i位数,最高位是j类型(j==0,数字1;j==1,数字3;j==2,其他),k表示是否(1/0)包含“13”,余数为m的数字有多少。

cnt[ i ][ k ][ m ] = dp[ i ][ 0/1/2 ][ k ][ m ]

dfs()函数,dfs(int pos,int pre,int h13,int m,bool fg)

pos:需要填的最高位

pre:前面一位是否(1/0)为1

h13:前面是否(1/0)已经有“13”了

fg:前面每一位都达到了最大否


//#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<cctype>
#include<string>
#include<algorithm>
#include<iostream>
#include<ctime>
#include<map>
#include<set>
using namespace std;
#define MP(x,y) make_pair((x),(y))
#define PB(x) push_back(x)
typedef __int64 LL;
//typedef unsigned __int64 ULL;
/* ****************** */
const int INF=100011122;
const double INFF=1e100;
const double eps=1e-8;
const LL mod=9999991;
const int NN=10010;
const int MM=2000010;
/* ****************** */

int dp[15][3][2][13];
int cnt[15][2][13];
int p[15],m13[250];
int a[15];

void init(int n)
{
    int i,j,k,m,x,nj,nk,nm,t;
    memset(dp,0,sizeof(dp));
    memset(cnt,0,sizeof(cnt));
    dp[0][2][0][0]=1;
    p[0]=1;
    for(i=1;i<11;i++)
    {
        p[i]=p[i-1]*10%13;
    }
    for(i=0;i<200;i++)
    {
        m13[i]=i%13;
    }

    for(i=0;i<n;i++)
        for(j=0;j<3;j++)
            for(k=0;k<2;k++)
                for(m=0;m<13;m++)
                    if(dp[i][j][k][m])
                    {
                        t=dp[i][j][k][m];
                        for(x=0;x<10;x++)
                        {
                            if(x==1)nj=0;
                            else if(x==3)nj=1;
                            else nj=2;
                            if( k==1 || (j==1&&x==1) )
                                nk=1;
                            else
                                nk=0;
                            nm=m13[ p[i]*x+m ];
                            dp[i+1][nj][nk][nm]+=t;
                            cnt[i+1][nk][nm]+=t;
                        }
                    }
}

int dfs(int pos,int pre,int h13,int m,bool fg)
{
    if(pos==0)
    {
        return (h13==1 && m==0);
    }
    if(!fg)
    {
        int ans,needm=m13[ m*p[pos] ];
        needm=m13[ 13-needm ];

        ans=cnt[pos][1][needm];
        if(h13==1)
        {
            ans+=cnt[pos][0][needm];
        }
        else if(pre==1)
        {
            ans+=dp[pos][1][0][needm];
        }
        return ans;
    }
    int i,ans=0;
    for(i=0;i<=a[pos];i++)
    {
        if(h13 || (pre==1&&i==3))
            ans+=dfs(pos-1,(i==1),1,m13[m*10+i],i==a[pos]);
        else
            ans+=dfs(pos-1,(i==1),0,m13[m*10+i],i==a[pos]);
    }
    return ans;
}

int calc(int x)
{
    int tol=0;
    do
    {
        a[++tol]=x%10;
        x/=10;
    }while(x);
    return dfs(tol,0,0,0,1);
}


int main()
{
    init(10);
    int n,ans;
    while(scanf("%d",&n)!=EOF)
    {
        ans=calc(n);
        printf("%d\n",ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值