[HAOI2010] 计数 - 数位dp,组合数

你有一组非零数字(不一定唯一),你可以在其中插入任意个0,这样就可以产生无限个数。比如说给定{1,2},那么可以生成数字12,21,102,120,201,210,1002,1020,等等。

现在给定一个数,问在这个数之前有多少个数。(注意这个数不会有前导0).

Solution

可重复康托展开

  • 常用数位 dp 套路,枚举哪一位开始比原数小,前方唯一而后方算全排列
  • 回避高精度的全排列数计算,考虑到阶乘最多大约算到 50, 开个因子计数桶,然后在桶上操作就可以了
#include <bits/stdc++.h>
using namespace std;

#define int long long
const int N = 1005;
char str[N];
int n;

int buc[N];

void push(int x) {
    for(int i=2;i<=x;i++) {
        while(x%i==0) buc[i]++, x/=i;
    }
}

void pop(int x) {
    for(int i=2;i<=x;i++) {
        while(x%i==0) buc[i]--, x/=i;
    }
}

int calc(vector <int> v) {
    int sum=0;
    for(int i=0;i<10;i++) sum+=v[i];
    for(int i=2;i<=sum;i++) push(i);
    for(int i=0;i<10;i++) {
        for(int j=2;j<=v[i];j++) pop(j);
    }
    int ans=1;
    for(int i=2;i<=50;i++) while(buc[i]) buc[i]--, ans*=i;
    return ans;
}

signed main() {
    cin>>str+1;
    n=strlen(str+1);
    vector <int> v;
    for(int i=0;i<10;i++) v.push_back(0);
    for(int i=1;i<=n;i++) v[str[i]-'0']++;
    int ans=0;
    for(int i=1;i<=n;i++) {
        for(int j=0;j<str[i]-'0';j++) {
            if(v[j]==0) continue;
            v[j]--;
            ans+=calc(v);
            v[j]++;
        }
        v[str[i]-'0']--;
    }
    cout<<ans<<endl;
}

原文地址:https://www.cnblogs.com/mollnn/p/12317129.html

时间: 2024-11-05 19:31:09

[HAOI2010] 计数 - 数位dp,组合数的相关文章

BZOJ 2425 [HAOI2010]计数:数位dp + 组合数

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=2425 题意: 给你一个数字n,长度不超过50. 你可以将这个数字: (1)去掉若干个0 (2)打乱后重新排列 问你可以产生多少个小于n的数字. 题解: 题目中的第一个操作其实是没有用的. 去掉若干个0之后再重新排列(不允许前导0),和不去0直接重新排列(允许前导0),其实是等价的. 所以按照数位dp的方法从高到低按位统计. 如n = 2345时,分别统计前缀为0~1, 20~22, 23

【BZOJ-1833】count数字计数 数位DP

1833: [ZJOI2010]count 数字计数 Time Limit: 3 Sec  Memory Limit: 64 MBSubmit: 2494  Solved: 1101[Submit][Status][Discuss] Description 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. Input 输入文件中仅包含一行两个整数a.b,含义如上所述. Output 输出文件中包含一行10个整数,分别表示0-9在[a,b]中出现了多少次.

bzoj1833: [ZJOI2010]count 数字计数(数位DP+记忆化搜索)

1833: [ZJOI2010]count 数字计数 题目:传送门 题解: 今天是躲不开各种恶心DP了??? %爆靖大佬啊!!! 据说是数位DP裸题...emmm学吧学吧 感觉记忆化搜索特别强: 定义f[i][j][k]表示若前i个位置有k个j的此时的全局方案数,然后就可以记忆化搜索了(具体看代码吧) 代码: 1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<cmath>

BZOJ 1833 ZJOI 2010 count 数字计数 数位DP

题目大意:问0~9这10个数字在[l,r]中出现过多少次. 思路:数位DP.以前只是听说过,并没有写过,写了才发现好闹心啊.. 预处理一个数组,f[i][j][k]表示长度为i,开头为j,数字k出现的次数. 对于一个数kXXXXXX,我们先处理1~999999,然后处理1000000~kXXXXXX 前面的东西很规则,可以直接调用f数组来解决. 对于后面不太规则的东西,按位处理.总之就是乱搞,我也不太懂说不明白... CODE: #include <cstdio> #include <c

BZOJ 1833 数字计数(数位DP)

经典数位DP模板题. # include <cstdio> # include <cstring> # include <cstdlib> # include <iostream> # include <vector> # include <queue> # include <stack> # include <map> # include <set> # include <cmath>

BZOJ1833: [ZJOI2010]count 数字计数 (数位dp)

传送门 数位dp... ...大概都是这个套路吧... ... 写这个的时候直接水了一发... ...我也不知道自己写的是不是dp... ... 大概是主要内容和dp关系不大的dp... ... mark代码..细长的代码真是丑啊..换行太频繁了.... 1 #include<cmath> 2 #include<cstdio> 3 #include<cstring> 4 #include<iostream> 5 #include<algorithm&g

【BZOJ1833】【ZJOI2010】数字计数 数位DP

链接: #include <stdio.h> int main() { puts("转载请注明出处[辗转山河弋流歌 by 空灰冰魂]谢谢"); puts("网址:blog.csdn.net/vmurder/article/details/46444975"); } 题解: 然而并没有DP. [1,R]的答案减去[1,L]的答案. 对于一个数 X ,求 [1,X] 的答案,我是先处理出 [1,999--9] 的答案(那个999--9 < X) 然后按

题解——[ZJOI2010]数字计数 数位DP

最近在写DP,今天把最近写的都放上来好了,,, 题意:给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. 首先询问的是一个区间,显然是要分别求出1 ~ r ,1 ~ l的答案,然后相减得到最终答案 首先我们观察到,产生答案的区间是连续的,且可以被拆分, 也就是说0 ~ 987的贡献= 0 ~ 900 + 901 ~ 987的恭喜, 同理,把位拆开也是等价的,所以我们可以单独计算每个位的贡献 这样讲可能有点不太清晰,举个例子吧 3872 我们先把它按数拆开来

BZOJ 1833 ZJOI2010 count 数字计数 数位DP

题目大意:求[a,b]间所有的整数中0~9每个数字出现了几次 令f[i]为i位数(算前导零)中每个数出现的次数(一定是相同的,所以只记录一个就行了) 有f[i]=f[i-1]*10+10^(i-1) 然后照例十进制拆分 其中计算[0,999...9]的时候要从1~9枚举最高位,然后其余位调用f[i-1]即可 剩余部分已知位直接乘,未知位调用f[i] #include<cstdio> #include<cstring> #include<iostream> #includ