数位DP——计数问题+Amount of Degrees+数字游戏

传送门:计数问题

思路:

举例:当n=abcdef这个七位数的时候,求1在第4位上面出现的次数。

有1<=xxx1yyy<=abcdefg

求1在第4位出现的次数

(1)当xxx=000~abc-1,此时的方案数为edg*1000

(2)当xxx=abc

情况1:d<1,abc1yyy>abcdecg ,方案数为0

情况2:d=1,yyy=000~efg,方案数为efg+1

情况3:d>1,yyy=000~999,方案数为1000

这些还可以沿用到其他的数字用到其他位数的情况

除了0要特判。

除了0要特判。

除了0要特判。

定义一个函数count(n,x),计算1~n中x出现的次数。

当要求某一个区间中x出现的次数时就直接计算count(r,x)-count(l-1,x)

代码:

#include<bits/stdc++.h>
using namespace std;
int count(int n,int i)
{
    int res=0;
    int k=0;
    int tmp=n;
    while(tmp)
    {
        k++;
        tmp/=10;
    }
    for(int j=1;j<=k;j++)
    {
        int p=pow(10,j-1);
        int l=n/p/10;  ///计算第j位左边的数字
        int r=n%p;     ///计算第j位右边的数字
        int d=n/p%10;  ///计算第j位上的数字

        if(i) res+=l*p;  ///不为0的情况
        else if(!i&&l) res+=(l-1)*p; ///为0的情况,从001到abc-1

        if(d>i&&(i||l)) res+=p;  ///
        if(d==i&&(i||l)) res+=r+1;///i为0时只要左边abc不为0即可
    }
    return res;
}
int main()
{
    int a,b;
    while(scanf("%d%d",&a,&b),a,b)
    {
        if(a>b) swap(a,b);
        for(int i=0;i<=9;i++)
            printf("%d ",count(b,i)-count(a-1,i));
        puts("");
    }
        return 0;
}

传送门:度的数量 

思路:数形DP有很多都有一个同用的特性,就是在求某一个区间里面的答案的数量时可以采用类似前缀和的思想得到的在区间[l,r]之间的答案就是ans[r]-ans[l-1].用在上面的题目和这道题目都一样。

这道题目就是要求在某一个区间里面所有数的B进制表示下有几个数的所有位数上面是有k个1的,拿题目样例做说明就是在15~20之间有

17:10001          18:10010     20:10100,有三个数的B进制下是有K个1。

在求1~n直接的反感数的时候,我们要先得到n的B进制表示形如an-1~a0

用一个last记录已经使用的1的数量的,枚举到第i位时只要该位上的数字不为0,可以考虑将一个0放在这一位,那么还剩下k-last个数要放到剩下的位数里面,答案要加上C(n-i,k-last),如果这一位是大于1的那么这一位可以直接取1,剩下的不管怎么取都不会超范围,加上C(n-i,k-last-1)之后就可以直接退出循环,当第i位等于1时无法确定后面的位数的取法,需要继续下去处理不同的情况。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=35;
int  f[N][N];
int k,b;
void init()
{
    for(int i=0;i<N;i++)
        for(int j=0;j<=i;j++)
        if(j==0) f[i][j]=1;
    else f[i][j]=f[i-1][j]+f[i-1][j-1];
}
int dp(int n)
{
    if(!n) return 0;
    vector<int>nums;
    while(n) nums.push_back(n%b),n/=b;
    int res=0;
    int last=0;
    for(int i=nums.size()-1;i>=0;i--)
    {
        int x=nums[i];
        if(x)
        {
            res+=f[i][k-last];
            if(x>1)
            {
                if(k-last-1>=0)
                res+=f[i][k-last-1];
                break;
            }else{
                last++;
                if(last>k) break;
            }
        }
        if(!i&&last==k) res++;
    }
    return res;
}
int main()
{
    init();
    int l,r;
    cin>>l>>r>>k>>b;
    cout<<dp(r)-dp(l-1);

        return 0;
}

传送门:数字游戏

思路:将n看成an-1~a0一个n位数;同样划分两部分

第n-1位可以取0~a(n-1)-1和a(n-1)

当取0~a(n-1)-1时,后面的n-1位数可以任意取,变成在n-1位数中有多少个非下降数。

定义一个状态f[i,j],表示所有最高位是j,一共有i位的不降数的集合的数量。

状态划分: f[i,j]中的元素的第一位都是填j,从第二位开始划分,如果第二位填的是k,那么f[i,j]的状态组成就是所有的f[i-1][k](忽略最高位只看后面的位数),这里的k是所有大于等于j小于等于9的数。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=15;
int f[N][N];
int a,b;
void init()
{
    for(int i=0;i<=9;i++) f[1][i]=1;
    for(int i=2;i<N;i++)
        for(int j=0;j<=9;j++)
        for(int k=j;k<=9;k++)
        f[i][j]+=f[i-1][k];
}
int dp(int n)
{
    if(!n) return 1;///0的情况下只有0一种情况,可直接返回。

    vector<int>nums;
    while(n){
        nums.push_back(n%10);
        n/=10;
    }
    int res =0;
    int last=0;///存上一位数字,保证一位不小于上一位。

    for(int i=nums.size()-1;i>=0;i--)
    {
        int x=nums[i];
        for(int j=last;j<x;j++)///当第i位取0~a[i]-1的情况,还剩
        res+=f[i+1][j];  ///包括当前这一位且最高位是j的情况下的方案数

        if(x<last) break;//
        last=x;
        if(!i) res++;//到最低位的情况,说明前面都已经是非降序分布了
    }
    return res;
}
int main()
{
    init();
    while(scanf("%d%d",&a,&b)!=EOF)
    {

        printf("%d\n",dp(b)-dp(a-1));
    }
        return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值