[HAOI2010]计数

题目描述

你有一组非零数字(不一定唯一),你可以在其中插入任意个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 ;
}
posted @ 2018-10-31 21:15  beretty  阅读(157)  评论(0编辑  收藏  举报