洛谷·[HAOI2010]计数

初见安~这里是传送门:洛谷P2518 计数

题目描述

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

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

输入格式

只有1行,为1个整数n.

输出格式

只有整数,表示N之前出现的数的个数。

输入 

1020

输出 

7

说明/提示

n的长度不超过50,答案不超过2^63-1.

题解

这是我首个自己写出来的数位dp题哎!!!!!!!!!【虽然最后好像并没有用到dp?……】

很容易发现的是,其实就是求给出的n的每一位数一起组成的所有排列里有多少个数比n小。就比如1020,前面有12,其实就是排列0012。

知道这一点过后其实就简单多了。我们从大到小枚举每一位,看看还剩下多少没有固定的比原位严格小的数可以填上去,就比如1020,第一位上原数是1,我们有\begin{Bmatrix} 0,1,2 \end{Bmatrix},就可以填0上去,这样的话保证了比1小,后面的三位就可以随便组合一下了。以此类推,每计算过一位,这一位就固定为原数值。这里用到了数位dp的思想吧。

接下来看如果算到了第i位,后面的n-i位的答案怎么统计。因为题目说了,每个数可能会出现多次,我们就不能单纯地用组合公式阶乘。既然每个数都有多个,那我们就分开考虑吧——假设统计到了第i位,剩下n-i位,剩下的数分别有cnt_0,cnt_1,cnt_2...cnt_9个,如果有x\in[0,9],cnt_x>0,我们就可以在剩下的n-i位中选cnt_x个位置给x,其选取方式有C_{n-i}^{cnt_x}种。这时还剩n-i-cnt_x个位置。考虑下一个满足条件的x,以此类推。最后所有的组合相乘就是后面n-i位可提供的答案数量。

求组合数的方式很多,这里选择的是数论。

上代码——

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define int long long
using namespace std;
typedef long long ll;
int read() {
	int x = 0, f = 1, ch = getchar();
	while(!isdigit(ch)) {if(ch == '-') f = -1; ch = getchar();}
	while(isdigit(ch)) x = (x << 1) + (x << 3) + ch - '0', ch = getchar();
	return x * f;
}

ll C(ll n, ll m) {//数据范围不大,暴力求是可以的
	if(!m) return 0;
	ll a = 1, b = 1;
	for(int i = n - m + 1; i <= n; i++) {
		a *= i;
		while(b <= m && a % b == 0) a /= b, b++;
	}
	return a;
}

int n, num[55], cnt[20], sum = 0;
ll cal(int p) {
	int a = sum; ll tot = 1;
	for(int i = 0; i <= 9; i++) {//分别计算组合数
		if(cnt[i]) tot *= C(a, cnt[i]), a -= cnt[i];
	}
	return tot;
}

char tmp[55];
ll solve() {
	ll ans = 0; sum = n;
	for(int i = 1; i < n; i++) {
		if(!sum) break;
		for(int j = 0; j < num[i]; j++) {//看小于这一位的数
			if(cnt[j]) cnt[j]--, sum--, ans += cal(n - i), cnt[j]++, sum++;
		}//上面一行:如果可以选j,那就先去掉j,统计组合数,再加回来。
		cnt[num[i]]--; sum--;//最后这一位固定,sum--
	}
	return ans;
}

signed main() {
	scanf("%s", tmp);
	n = strlen(tmp);
	for(int i = 0; i < n; i++) {
		cnt[tmp[i] - '0']++, num[i + 1] = tmp[i] - '0';
	}
	
	printf("%lld\n", solve());
	return 0;
}

迎评:)
——End——

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值