题目描述
你有一组非零数字(不一定唯一),你可以在其中插入任意个0,这样就可以产生无限个数。比如说给定{1,2},那么可以生成数字12,21,102,120,201,210,1002,1020,等等。
现在给定一个数,问在这个数之前有多少个数。(注意这个数不会有前导0).
输入输出格式
输入格式:
只有1行,为1个整数n.
输出格式:
只有整数,表示N之前出现的数的个数。
输入输出样例
输入样例#1
1020
输出样例#1
7
说明
n的长度不超过50,答案不超过2^63-1.
题解
类似于数位dp
求比这个排列小的排列个数
但是元素有重复
可重复元素的排列个数是 : 排列的长度的阶乘 / 所有元素出现次数的阶乘之积
所以我们可以考虑一个比较暴力的方法 :
枚举每一位放什么
就类似于数位dp
如果这一位是x
那么放0~x-1且在原排列中存在的数就是不卡上界的
后面的可以随便放
如果放x,那么就是卡上界的
就需要继续枚举下一位来处理情况
然后本题在计算过程中会爆longlong
注意处理
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
# define LL long long
const int M = 55 ;
const int N = 13 ;
using namespace std ;
int n ;
char s[M] ;
int t[N] ;
LL Ans , fac[M] ;
inline LL Solve(int n) {
LL ret = 1LL ;
bool vis[N] ;
memset(vis , false , sizeof(vis)) ;
for(int i = 1 ; i <= n ; i ++) {
ret *= i ;
for(int i = 0 ; i <= 9 ; i ++)
if(!vis[i] && ret % fac[t[i]] == 0)
ret /= fac[t[i]] , vis[i] = true ;
}
return ret ;
}
int main() {
scanf("%s",s + 1) ; n = strlen(s + 1) ;
for(int i = 1 , x ; i <= n ; i ++) {
x = s[i] - '0' ;
++ t[x] ;
}
fac[0] = 1 ;
for(int i = 1 ; i <= 18 ; i ++) fac[i] = fac[i - 1] * i ;
for(int i = 1 , x ; i <= n ; i ++) {
x = s[i] - '0' ;
for(int j = 0 ; j < x ; j ++) {
if(!t[j]) continue ;
--t[j] ; // 这一位选择不卡上界的j
Ans += Solve(n - i) ;
++t[j] ;
}
--t[x] ; // 这一位选择卡上界的x,因为如果到了这一位说明上一位也卡上界了,那么就去枚举下一位是什么
}
printf("%lld\n",Ans) ;
return 0 ;
}