题目:https://www.luogu.org/problemnew/show/P2518
题目描述
你有一组非零数字(不一定唯一),你可以在其中插入任意个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中limit(即是否有最大值限制)的用法。
设原数为a,第i位为a[i],长度为n。
这个数中0~9 出现次数为tot[1],tot[2],tot[3]。。。。。。tot[9]。
先从最高位n开始,现在尝试往最高位上放数x。
如果0≤x<a[n],那么后面的数就没有限制了,在剩下的数中随便取。
设这种情况的方案数怎么求?
第一位为x确定,所以tot[x]--;
我们考虑往剩下的n-1位中填数。
0要填tot[0]次,1要填tot[1]次,...,9要填tot[9]次。
我们先把0填上,那么填上0的方案数为c(n-1,tot[0]),
(c为组合数)
剩下只有n-1-tot[0]个位置了,
所以填1的方案数位c(n-1-tot[0],tot[1]),
以此类推,方案数遵循乘法原理,这种情况的方案数为:
c(n-1,tot[0])*c(n-1-tot[0],tot[1])*...*c(n-1-tot[0]-tot[1]-...-tot[8],tot[9]);
如果x==a[n],那么只要保证剩下n-1位全排列符合条件就可以。
求剩下n-1位的方法就同 求n位的方法了。
详情参照代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 #include<cmath> 6 #include<cstring> 7 using namespace std; 8 #define ll long long 9 string ra; 10 ll a[100],n; 11 ll tot[100],tmp,m; 12 ll c[100][100]; 13 ll ans; 14 void get_zuhe(){ 15 for (int i=0;i<=60;++i){ 16 c[i][0]=1; 17 } 18 for (int i=1;i<=60;++i){ 19 for (int j=1;j<=i;++j){ 20 c[i][j]=c[i-1][j]+c[i-1][j-1]; 21 } 22 } 23 } 24 int main(){ 25 cin>>ra; 26 n=ra.size(); 27 for (ll i=1;i<=n;++i){ 28 a[i]=ra[n-i]-‘0‘; 29 tot[a[i]]++; 30 } 31 get_zuhe(); 32 for (ll i=n;i>=1;--i){ 33 for (ll j=0;j<a[i];++j){ 34 if (tot[j]){ 35 tot[j]--; 36 tmp=1; m=i-1; 37 for (ll k=0;k<=9;++k){ 38 if (tot[k]){ 39 tmp*=c[m][tot[k]]; 40 m-=tot[k]; 41 } 42 } 43 ans+=tmp; 44 tot[j]++; 45 } 46 } 47 tot[a[i]]--; 48 } 49 cout<<ans; 50 return 0; 51 }