#295 (div.2) E.Pluses everywhere

1.题目描述:点击打开链接

2.解题思路:本题是一道组合数学题,一开始用递归的思想做,但结果错误。学习了别人的解法后,豁然开朗。正确的解法是关注每一位数对整体的贡献值。比如输入的n位数是D1D2D3...D(n-1)D(n),那么当D(i)作为个位数时,它的前面必然有一个‘+’。剩下的k-1个‘+’被安置在剩下的n-2个空隙中,因此一共有C(n-2,k-1)种情况,D(i)的总贡献值是D(i)*C(n-2,k-1);同理,当D(i)作为十位数时,D(i+1)必然是个位数,D(i+1)前面必然有一个‘+’,那么剩下的k-1个‘+’被安置在剩下的n-3个空隙中,因此有C(n-3,k-1)种情况,D(i)的总贡献值是D(i)*10*C(n-3,k-1),以此类推。这样的D(i)最多只能是在第n-k位(个位算第1位,十位算第2位,依次类推),这样便计算出了第一部分贡献值。

第二部分的贡献值来自‘+’后面的那位数。假设D(i+1)前有一个‘+’且它恰好是第n位时,必然是k个‘+’被安置在它前面的n-1个空隙中,因此有C(n-1,k)种情况,那么D(i+1)的总贡献值是D(i+1)*C(n-1,k);若D(i+1)前有一个‘+’且它恰好是第n-1位,那么总贡献是D(i+1)*10*C(n-2,k),以此类推。最后,将两部分之和相加就是最终的答案。

由于涉及组合数取模,前缀和。因此应该事先在初始化中计算出这些数的值,便于后续处理。根据组合数公式:C(n,k)=n!/k!/(n-k)!,因此应当事先计算出n!(mod M)的值。由于本题中M是一个素数,可以考虑用逆元来代替除法。根据费马小定理易知:n^(M-1)≡1(mod M),即n*n^(M-2))≡1(mod M)。因此n的逆元是n^(M-2)。这是本题第一个新知识点。计算出了n!的逆元之后,再根据n!=n*(n-1)!,两边同时乘以INV(n),INV(n-1),化简得:INV(n-1)=n*INV(n),因此可以利用该公式递推求出其他的逆元,这是本题的第二个新知识点。本题通过考虑每一位数贡献值的角度则是第三个新知识点。

注意:输出long long型时推荐用%I64d输出。

3.代码:

#define _CRT_SECURE_NO_WARNINGS 
#include<iostream>
#include<algorithm>
#include<string>
#include<sstream>
#include<set>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<functional>
using namespace std;

typedef long long LL;
const int MOD = 1000000000 + 7;
const int N = 110000;
int n, k, a[N],sum[N];
char st[N];
LL fac[N], inv[N];

LL pow_mod(LL a, LL k)
{
	LL ans = 1;
	while (k > 0)
	{
		if (k & 1)ans = ans*a%MOD;
		a = a*a%MOD;
		k >>= 1;
	}
	return ans;
}
void init()//计算阶乘取模,逆元
{
	fac[0] = 1;
	for (int i = 1; i <= n; i++)
		fac[i] = fac[i - 1] * i%MOD;
	inv[n] = pow_mod(fac[n], MOD - 2);
	for (int i = n - 1; i >= 0; i--)
		inv[i] = inv[i + 1] * (i + 1) % MOD;
}
int C(int n, int k)//计算组合数取模
{
	return fac[n] * inv[k] % MOD*inv[n - k] % MOD;
}
int main()
{
	//freopen("t.txt", "r", stdin);
	scanf("%d%d", &n, &k);
	scanf("%s", st + 1);
	sum[0] = 0;
	for (int i = 1; i <= n; i++)
	{
		a[i] = st[i] - '0';
		sum[i] = sum[i - 1] + a[i];
	}
	init();
	LL ans = 0;
	LL s;
	LL base = 1;//base一定要设为LL!
	for (int i = 1; i <= n - k; i++)
	{
		s = (LL)sum[n - i] * base%MOD;//第一部分的贡献
		ans += (LL)s*C(n - i - 1, k - 1) % MOD;

		s = (LL)a[n - i + 1] * base%MOD;//第二部分的贡献
		ans += s*C(n - i, k) % MOD;

		base *= 10;
		ans %= MOD;
		base %= MOD;
	}
	printf("%I64d\n", ans);
	return 0;
}



你可能感兴趣的:(阶乘,前缀和,逆元,模运算,组合计数)