51 nod 加号分配 组合数学(逆元,快速幂)

1528 加号分配
题目来源: CodeForces

基准时间限制:1 秒 空间限制:131072 KB 分值: 160 难度:6级算法题 

题目描述:

现在要给一个长度为n数字串上面加上恰好k个加号,把所有可能的算术结果相加起来。

加号加到数字串中间之后要形成正确的算术表达式。规则是:没有两个加号连在一起,两个加号之间至少要有一位数字,加号不能加在开头,也不能加在结尾。比如数字串是10500,那么100500(加0个加号),1+00+500 或者 10050+0 这些放置的加号都是合法的,而100++500, +1+0+0+5+0+0 和100500+都是非法的。
结果比较大,对 109+7 取余输出即可。
样例解释:
在第一个例子中 (1+08)+(10+8)=27。
在第二个例子中 1+0+8=9。
Input
单组测试数据。
第一行有两个整数n 和k (0≤k<n≤10^5)。
第二行包含n位数字。
Output
输出结果占一行。
Input示例
样例输入1
3 1
108
样例输入2
3 2
108
Output示例
样例输出1
27
样例输出1

9

题解:对于此类问题通常是讲n个数一位一位来看的每一个数可以作为各位数,前n-1个数可以作为十位数,前n-2位数可以作为百位数……末尾到首开始看每一位对总和的贡献=
倒数第一位:贡献了C(n-1,k)次个位数
倒数第二位:贡献了C(n-2,k-1)次个位数,C(n-2,k)次十位数
倒数第三位:贡献了C(n-2,k-1)次个位数,C(n-3,k-1)次十位数,C(n-3,k)次
倒数第四位:贡献了C(n-2,k-1)次个位数,C(n-3,k-1)次十位数,C(n-4,k-1)次百位数,C(n-4,k)次千位数

1.求答案,考虑每个数作为i位数(可为答案贡献10的i-1次方,个位i=1,十位i=2,...,最多n-k位):
那么它及后面 共i个数 之间不能有加号。
且只有前n-i+1个数可以作为i位,如果是an-i+1作为i位,那么后面都不能有加号,k个加号在a1到an-i+1之间,所以有C(n-i,k)次贡献(这么说怪怪的→_←),就是几种情况。
a1 a2 a3 ... an-i+1 ... an
如果是a1、a2、...an-i作为i位,比如a1,那就是ai和ai+1之间用掉一个加号,其它加号ai+1的后面n-1-i个间隔里。
a1 a2 a3 ... ai + ai+1 ...  an
再比如a2,剩下k-1个加号在ai+2的后面及a1和a2之间 n-1-i个间隔里。
a1 a2 a3 ... ai+1 + ai+2 ...  an
所以他们都做了C(n-i-1,k-1)次贡献。
于是就有ans=∑(i=1到n-k)[(a1+a2+...an-i)*C(n-i-1,k-1)+an-i+1*C(n-i,k)]%M。
s[i]为前缀和,ans=∑(i=1到n-k)[s[n-i]*C(n-i-1,k-1)+an-i+1*C(n-i,k)]%M。
2.组合数取模
由费马小定理,当a和p互质时:ap-1≡1 mod p 可得 a*ap-2≡1 mod p,ap-2和a互为逆元。
a/b mod p=a*b-1 mod p 也就是求除数的逆元。
C(a,b)=a!/[b!*(a-b)!]
所以先求出所有1到n的阶乘,和它的逆。

计算出了n!的逆元之后,再根据n!=n*(n-1)!,两边同时乘以INV(n),INV(n-1),化简得:INV(n-1)=n*INV(n),因此可以利用该公式递推求出其他的逆元

说了这么多,就一句话:对于每一位分析它作为第i位对答案的贡献值。

总结:做题中一定要有拆的思想,因为涉及到方案数,所以很有可能和组合数学有关,而且在不同的情况下每一位的状态是不同的,所以应该把状态分开来看。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define mod 1000000007
#define N 100005
using namespace std;
int n,k;
long long date[N],s[N];
char str[N];
long long re[N],inv[N];
long long pow(long long x)//快速幂 
{
	long long y=mod-2,ans=1;
	while(y)
	{
		if(y&1) ans=ans*x%mod;
		y>>=1;
		x=x*x%mod;
	}
	return ans;
}
void init()//求阶乘+逆元 
{
	re[0]=1;
	for(int i=1;i<=n;i++)
	    re[i]=re[i-1]*i%mod;
	inv[n]=pow(re[n]);
	for(int i=n-1;i>=0;i--)
	    inv[i]=inv[i+1]*(i+1)%mod;//o(n)求逆元 
}
long long C(long long a,long b)
{
	return re[a]*inv[b]%mod*inv[a-b]%mod; 
}
int main()
{
//	freopen("in.in","r",stdin);
//	freopen("my.out","w",stdout);
	long long ans=0,dase=1,print=0;s[0]=0;
	scanf("%d%d",&n,&k);
	scanf("%s",str+1);
	for(int i=1;i<=n;i++) date[i]=str[i]-'0',s[i]=s[i-1]+date[i];
	init();
	for(int i=1;i<=n-k;i++)
	{
		ans=(ans+s[n-i]%mod*dase%mod*C(n-i-1,k-1)%mod)%mod;
		ans=(ans+date[n-i+1]%mod*dase%mod*C(n-i,k)%mod)%mod;
		dase=dase*10%mod;
	}
	printf("%lld\n",ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值