[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;
}

 

posted @ 2018-04-15 14:08  qjs12  阅读(101)  评论(0编辑  收藏  举报