Codeforces 521C (经典)组合数取模【逆元】

<题目链接>

<转载于 >>>  >

题目大意:
给出一串n个数字,让你在这串数字中添加k个 ' + ' 号(添加后表达式合法),然后所有拆分所得的所有合法表达式之和。

解题分析:

首先,暴力的做法肯定是不可行的,复杂度必然爆炸。然后来考虑怎么求每个数字在最终结果里的贡献呢。我们这n个数字有n-1个空位置,来放置k个' + '号,不论哪一种放置方法,每个数字都要在这种情况里出现一次,但是出现时所充当的分位是不同的,这就是统计贡献的地方!再由于每个数字都是不同的单独的,所以单独考虑每个数字可能所成为的位数时不会出现重复。

第n位:只能充当某个数字的个位有C(n-1,k)种个位情况

第n-1位:能充当个位(加号必须放一个在它后面才它能成为个位),能充当十位(加号必须放一个在它后面的后面才能使他成为十位并且他和个位之间不能放置东西)   个位:C(n-2,k-1)    十位:C(n-2,k)

第n-2位:同理   个位:C(n-2,k-1)   十位:C(n-3,k-1)  百位:C(n-3,k)

.........

也就是说:

倒数第一位:贡献了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)次千位数

........

倒数第n位:贡献了C(n-2,k-1)次个位数,C(n-3,k-1)次十位数,C(n-4,k-1)次百位数,C(n-4,k)次千位数......C(n - n,k)次n位数

所以我们预处理一下组合数C(x,k-1)和C(x,k),类似求个前缀和就可以啦!

 

核心部分:

倒数第一位:C(n-1,k)*s[n]

倒数第二位:C(n-2,k-1)*s[n-1] + C(n-2,k)*10*s[n-1]

倒数第三位:(C(n-2,k-1) + C(n-3,k-1)*10)*s[n-2] + C(n-3,k)*100*s[n-2]

倒数第四位:(C(n-2,k-1) + C(n-3,k-1)*10 + C(n-4,k-1)*100)*s[n-3] + C(n-4,k)*1000*s[n-3]

...........

所以可以看出,每一位数字的前部分(红色)和是从第n位累和过来的,后部分(绿色)是单独处理的,所以我们可以从第n位,开始往前循环每一位到第一位,累前缀和求和即可。

#include <cstdio>
#include <cstring>
#include <algorithm>
const int M = 1e5+5;
const int mod = 1e9+7;
using namespace std;
typedef long long ll;
ll fac[M],inv[M],sum[M],a[M];
char s[M];
int n,k;
ll ans,mult;
ll mod_pow(ll x,ll n){
    x%=mod;
    ll res = 1;
    while(n>0){
        if(n&1) res = res*x%mod;
        x = x*x%mod;
        n>>=1;
    }
    return res;
}
void init(){
    inv[0]=1;fac[0]=1;
    for(int i=1;i<=n;i++){
        fac[i] = (fac[i-1]*i)%mod;   
        inv[i] = mod_pow(fac[i],mod-2)%mod;   //费马小定理预处理记录 i! 的逆元
    }
}
ll C(ll n,ll m){
    if(n<0||m<0) return 0;
    if(m>n) return 0;
    return (((fac[n]*inv[m])%mod)*inv[n-m])%mod;   //计算组合数C(n,m), n!*(m!的逆元)*((n-m)!的逆元)
}
int main()
{
    scanf("%d%d",&n,&k);
    init();
    scanf("%s",s+1);
    for(int i=1;i<=n;i++)
        a[i] = s[i]-'0';
    mult = 1;
    for(int i=n-1;i>=1;i--){
        sum[i] = (sum[i+1]+mult*C(i-1,k-1))%mod;    //计算该数位为红色部分的贡献,因为红色部分这一位与这一位+1的贡献有关,所以可以求部分前缀和,简化计算
        mult = (mult*10)%mod;
    }
    mult = 1;
    for(int i=n;i>=k+1;i--){
        sum[i] = (sum[i]+C(i-1,k)*mult)%mod;//计算该位数绿色部分的贡献
        mult = (mult*10)%mod;
    }
    ans = 0;
    for(int i=1;i<=n;i++)
        ans = (ans+sum[i]*a[i])%mod;    //乘上每个位数上的数
       printf("%lld\n",ans);
}

 

 

2018-10-09

转载于:https://www.cnblogs.com/00isok/p/9762672.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值