[HAOI2010]计数
可重集全排列公式。
从首位开始向后扫,对于第 i 位的数字 num[i],假设小于 num[i] 的数有 k 个,那么可以在第 i 位任选其中的一个,剩下的每一位可以随便取,对应的排列数 k*Π(sum-(j-i)) (i<j<=n),sum是所有可供使用的数字的数量,又因为有元素重复使用,再依次除以每个数字出现的次数的阶乘,相当于除以相同数的全排列,这就是可重集全排列公式。之后将第 i 为定位 num[i] ,对应 num[i] 的数量减1,依次递推下去。
注意两点。
1:直接乘会爆精度,因为要除以阶乘,可以预先统计每个除数要除的次数,之后边乘边除。
2:前导零可以看作位数不足的情况,并不会多算。
// q.c #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath> using namespace std; const int M=50+7; char num[M]; int cnt[12],s[12],dive[M],sum; long long ans,f[M]; bool vis[M]; int main() { freopen("perm.in","r",stdin); freopen("perm.out","w",stdout); scanf("%s",num+1); int n=strlen(num+1); for(int i=1;i<=n;i++) cnt[num[i]-'0']++; for(int i=1;i<=n;i++) { memset(dive,0,sizeof(dive)); s[0]=cnt[0]; for(int j=1;j<=9;j++) s[j]=s[j-1]+cnt[j]; for(int j=0;j<=9;j++) for(int k=2;k<=cnt[j];k++) dive[k]++; if(num[i]!='0') { long long ns=s[num[i]-'0'-1]; for(int j=i+1;j<=n;j++) { ns*=(s[9]-(j-i)); for(int k=2;k<=n;k++) if(ns%k==0&&dive[k]) ns/=k,dive[k]--; } for(int j=2;j<=n;j++) while(dive[j]) ns/=j,dive[j]--; ans+=ns; } cnt[num[i]-'0']--; } printf("%lld\n",ans); return 0; }