初见安~这里是传送门:洛谷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,我们有,就可以填0上去,这样的话保证了比1小,后面的三位就可以随便组合一下了。以此类推,每计算过一位,这一位就固定为原数值。这里用到了数位dp的思想吧。
接下来看如果算到了第i位,后面的n-i位的答案怎么统计。因为题目说了,每个数可能会出现多次,我们就不能单纯地用组合公式阶乘。既然每个数都有多个,那我们就分开考虑吧——假设统计到了第i位,剩下n-i位,剩下的数分别有个,如果有,我们就可以在剩下的n-i位中选个位置给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——