其他分享
首页 > 其他分享> > P2518 [HAOI2010]计数

P2518 [HAOI2010]计数

作者:互联网

题面

计数
这道题的题面上没有说清楚,在输入中出现的数字其实就是给定的集合。

题解

读完题后可能会没有思路,但是很快便不难发现,如果我们从左到右依次扫描每一个数字,如果后面有比这个数字小的数,就可以把这和数拿到这位来,在后面的数字中排出比它小的数。于是这道题就变成了可重集的排列问题。
因为不会康托展开的可重复集,所以我们可以想一个等效的办法。
设 a[i] 为元素大小为 i 的出现次数。
对于 m 个元素,我们从小到大的选出这些元素排到现有的位置中。
第一次选出元素 0 ,有 a[0] 个,剩余 m 个位置,于是有 C(m,a[0])。
第二次选出元素 1 ,有 a[1] 个,剩余 m - a[0] 个位置,于是有 C(m,a[0]) * C(m - a[0],a[1])。
……
最后的答案以此类推。

因为我们运用的是组合加乘法原理,这就可以等效于可重复集的排列。
因为对于一样的元素我们用的是组合,规避了重复元素重复拍的问题。
而对于不一样的元素我们用的是乘法原理,计算出了不重复元素的排列。
所以二者是等价的。

以上为对于当前扫到的位置的算法,所以我们对于每一位都要执行上述操作,一些小细节在代码中。

剩下的就是写代码时的技巧了,请看代码:

代码

#include<cstdio>
#include<cstring>

#define LL long long

const int N = 55;

LL c[N][N],ans = 0;

LL C(int n,int m) {
	if(c[n][m]) return c[n][m];
	if(m == 1) return n;
	if(m == 0 || m == n) return 1;
	if(m > n) return 0;
	return c[n][m] = C(n - 1,m) + C(n - 1,m - 1);
}

int a[N];
char s[N];

LL solve(int n) {
	LL res = 1;
	for(int i = 0; i <= 9; i++) if(a[i]) res *= C(n,a[i]),n -= a[i];
	return res;
}

int main() {
	scanf("%s",s + 1);
	int len = strlen(s + 1);
	for(int i = 1; i <= len; i++) ++a[s[i] - '0'];
	for(int i = 1,n = len - 1; i <= len; i++,n--) {
		for(int j = 0; j < s[i] - '0'; j++)
			if(a[j]) {--a[j]; ans += solve(n); ++a[j];}//--是因为把这个数与当前位交换后,它在后面出现的次数就会-1。
		--a[s[i] - '0'];//扫过了当前位,当前位在后面出现的次数就少了1。
	}
	printf("%lld",ans);
	return 0;
}

标签:return,P2518,int,LL,元素,计数,HAOI2010,代码,重复
来源: https://www.cnblogs.com/sjzyh/p/14838755.html