简单数位DP

不要62  http://acm.hdu.edu.cn/showproblem.php?pid=2089

求 (n,m)之间不含 4 和 62 的个数 , (0<n≤m<1000000),当然可以暴力打表过。 数位dp

DP[i][j]表示 从高位起 的 当前第i位状态为j的 且不在数值边界的 符合条件的 数字个数。上一位 是6 j=1,否则j=0;

记忆化搜索,注意数值边界不能用dp[][]记忆化。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<cmath>
#include<map>
#include<set>
using namespace std;
#define INF 1000000000
#define N 10
int dp[N][4],len;
char lim[N];
void tostring(int x)
{
    len=0;
    while(x) lim[++len]=x%10,x/=10;
}
int dfs(int i,int j,int flag)//i,j表示dp[i][j]中的意思,flag表示上位是否为边界
{
    if(i==1) return 1;
    if(!flag && dp[i][j]!=-1) return dp[i][j];//记忆化,边界没有记忆化 
    int bound=flag? lim[i-1]:9;//确定此位的数字上限
    int ret=0;
    for(int k=0;k<=bound;k++)
    {
        if(k==4) continue;
        if(j==1 && k==2) continue;
        ret+=dfs(i-1,k==6? 1:0,flag && k==bound);
    }
    return flag? ret:dp[i][j]=ret;//不记忆化 边界
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m),n||m)
    {
        n--;
        tostring(m);
        memset(dp,-1,sizeof(dp));
        int tmp1=dfs(len+1,0,1);
        tostring(n);
        memset(dp,-1,sizeof(dp));
        int tmp2=dfs(len+1,0,1);
        printf("%d\n",tmp1-tmp2);
    }

    return 0;
}
Bomb   http://acm.hdu.edu.cn/showproblem.php?pid=3555

求(1,N) 中 含有49的数字的个数, (1 <= N <= 2^63-1)。

dp[i][j]表示 当前为第 i 位,状态为j的在范围内 不是边界上 符合条件的 数字的个数,j=1表示上一位出现4,j=2表示第1到i-1位出现49,否则为j=0。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<cmath>
#include<map>
#include<set>
using namespace std;
#define INF 1000000000
#define N 25
typedef __int64 LL;
LL dp[N][4];// 0 none 1-->4  2-->49
LL t,n,len;
char lim[N];
void tostring()
{
    len=0;
    while(n) lim[++len]=n%10,n/=10;
}
LL dfs(int i,int j,int flag)
{
    if(i==1) return j==2;
    if(!flag && dp[i][j]!=-1) return dp[i][j];
    int bound=flag? lim[i-1]:9;
    LL ret=0;
    for(int k=0;k<=bound;k++)
    {
        if(j==2) ret+=dfs(i-1,2,flag&& k==bound);
        else if(k==9 && j==1) ret+=dfs(i-1,2,flag && k==bound);
        else if(k==4) ret+=dfs(i-1,1,flag && k==bound);
        else ret+=dfs(i-1,0,flag && k==bound);
    }
    return flag? ret:dp[i][j]=ret;
}
int main()
{
    scanf("%I64d",&t);
    for(LL i=1;i<=t;i++)
    {
        scanf("%I64d",&n);
        tostring();
        memset(dp,-1,sizeof(dp));
        printf("%I64d\n",dfs(len+1,0,1));
    }
    return 0;
}

Good Numbers  http://acm.hdu.edu.cn/showproblem.php?pid=4722

求(A,B)之间 ,各位上数字之和 为10 的 数字的个数  (0 <= A <= B <= 1018).

dp[i][j]  表示 第i位到末位的数字之和的MOD10==j的数字的个数,依旧是边界不能记忆化。 递归到最低位是只有j余数==0才return 1;

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<cmath>
#include<map>
#include<set>
using namespace std;
#define INF 1000000000
#define N 22
typedef __int64 LL;
LL t,len;
LL dp[N][12];
char lim[N];
void tostring(LL x)
{
    len=0;
    while(x) lim[++len]=x%10,x/=10;
}
LL dfs(int i,int j,int flag)
{
    if(i==1) return !j;
    if(!flag && dp[i][j]!=-1) return dp[i][j];
    int bound=flag? lim[i-1]:9;
    LL ret=0;
    for(int k=0;k<=bound;k++)
    {
        ret+=dfs(i-1,(10+j-k)%10,flag && k==bound);
    }
    return flag? ret:dp[i][j]=ret;
}
int main()
{
    scanf("%I64d",&t);
    LL a,b;
    for(LL i=1;i<=t;i++)
    {
        scanf("%I64d%I64d",&a,&b); a--;
        tostring(a);
        memset(dp,-1,sizeof(dp));
        LL tmp1=dfs(len+1,0,1);
        tostring(b);
        memset(dp,-1,sizeof(dp));
        LL tmp2=dfs(len+1,0,1);
        printf("Case #%I64d: %I64d\n",i,tmp2-tmp1);
    }

    return 0;
}

windy数   UESTC 1307 

不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。
windy想知道,在A和B之间,包括A和B,总共有多少个windy数? 1 <= A <= B <= 2000000000 。

dp[i][j]表示 当前为第i位 j>=0表示当前数字为j 满足条件的 数字的个数,j<0表示 第i位之前全是前导0,第j位可以为范围内的任意数,直到 某位不为0,j的状态改变。详见代码。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<cmath>
#include<map>
#include<set>
using namespace std;
#define INF 1000000000
typedef long long LL;
#define N 15
int dp[N][14],n,len;
char lim[N];
void tostring(int x)
{
    len=0;
    while(x) lim[++len]=x%10,x/=10;
}
int dfs(int i,int j,int flag)
{
    if(i==1) return 1;
    if(!flag && dp[i][j]!=-1) return dp[i][j];
    int bound=flag? lim[i-1]:9;
    int ret=0;
    for(int k=0;k<=bound;k++)
    {
        if(j<0 && k==0) ret+=dfs(i-1,-1,flag && k==bound);
        else if(j<0) ret+=dfs(i-1,k,flag && k==bound);
        else
        {
            if(abs(k-j)<2) continue;
            ret+=dfs(i-1,k,flag && k==bound);
        }
    }
    return flag? ret:dp[i][j]=ret;
}
int main()
{
    int a,b;
    while(~scanf("%d%d",&a,&b))
    {
        a--;
        tostring(b);
        memset(dp,-1,sizeof(dp));
        int tmp1=dfs(len+1,-1,1);
        tostring(a);
        memset(dp,-1,sizeof(dp));
        int tmp2=dfs(len+1,-1,1);
        printf("%d\n",tmp1-tmp2);
    }
    return 0;
}

B. Little Elephant and Elections    http://codeforces.com/problemset/problem/258/B

4和7是幸运数字。一共7个party,给这7个分别一个不同数字x,范围  (7 ≤ m ≤ 109),让一个特殊的party得到的数字中含有的幸运数字的个数,比其他6个party所有的数字中含有幸运数字的个数的总和还要多。 求多少种分配方案,使得满足条件。

首先 ,数位DP处理出来1~m之间 含有i个幸运数的数字的个数num[i] ,0<=i<=9; 

dp[i][j] 表示到第i位 且此位之前 含有j个幸运数 且满足最终 含有k个幸运数的范围内的 数字的个数。

num[i]表示m的范围内 含有i个幸运数的数字的个数。

之后枚举特殊的party的 选取的幸运数字个数, DFS 暴力求出方案数.

加入6个party中有r个选取了含有q个幸运数的数字此时的方案数=num[特殊party的选取]*(num[q]*(num[q]-1)......(num[q]-r+1))

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef __int64 LL;
#define INF 1000000000
#define N 15
#define MOD 1000000007
LL m,dp[N][N],num[N],len,sum=0,vis[N],par[N],vv[N],tmp;
char lim[N];
void tostring()
{
    len=0;
    while(m) lim[++len]=m%10,m/=10;
}
LL dfs(LL i,LL j,LL flag,LL k)
{
    if(j>k) return 0;
    if(i==0) return j==k;
    if(!flag && dp[i][j]!=-1) return dp[i][j];
    LL bound=flag? lim[i]:9;
    LL ret=0;
    for(LL p=0;p<=bound;p++)
    {
        if(p==4 ||p==7) ret+=dfs(i-1,j+1,flag && p==bound,k);
        else ret+=dfs(i-1,j,flag && p==bound,k);
    }
    return flag? ret:dp[i][j]=ret;
}
void dfs2(LL i,LL j)
{
    if(i>=7)
    {
        memset(vv,0,sizeof(vv));
        LL tt=1;
        for(LL k=1;k<=6;k++)
        {
            tt*=(num[par[k]]-vv[par[k]]);
            tt%=MOD;
            vv[par[k]]++;
        }
        tmp+=tt;
        return ;
    }
    for(LL k=0;k<=j;k++)
    {
        if(vis[k]>=num[k]) continue;
        vis[k]++;
        par[i]=k;
        dfs2(i+1,j-k);
        vis[k]--;
    }
}
int main()
{
    scanf("%I64d",&m);
    tostring();
    for(LL i=0;i<=9;i++)
    {
        memset(dp,-1,sizeof(dp));
        num[i]=dfs(len,0,1,i);
    }
    num[0]--;
    for(LL i=1;i<=9;i++)
    {
        if(!num[i]) continue;
        memset(vis,0,sizeof(vis));
        tmp=0;
        dfs2(1,i-1);
        tmp*=num[i];
        sum+=tmp;
        sum%=MOD;
    }
    printf("%I64d\n",sum);

    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值