Codeforces Round #104 (Div. 2) E - Lucky Subsequence

这个题其实就是个dp(类似背包),但是一些细节还是让我做了一晚上。

这个题学习了组合数取模(逆元法)

 

补充知识:逆元的求法

(a/b) mod p=a*(b逆) mod p

b*x=1(mod p) x就是b的逆元

而b逆可以利用扩展欧几里德或欧拉函数求得:

1).扩展欧几里德:b*x+p*y=1 有解,x就是所求

2).欧拉函数:b^(p-1)=1(mod p),故b*b^(p-2)=1(mod p),所以x=b^(p-2)
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<set>
#include<map>
#define ll long long
#define oo 1000000007
#define min(a,b) (a)<(b)?(a):(b)
using namespace std;
map <int,int> a;
int f[10005];
ll g[1500][1500],c[100005],s[10005];
ll xx,y;
void excuild(ll a,ll b)
    {
        ll tmp;
        if(b==0){xx=1;y=0;return;}
        excuild(b,a%b);
        tmp=xx;
        xx=y;
        y=tmp - a/b*y;
    }
int main()
{
        int h=0,t=1; f[1]=0;
        while (h<t){
                int i=f[++h]; if (i>100000000) continue;
                f[++t]=i*10+4; f[++t]=i*10+7;
                }              // 这里f[]记录小于100000000的幸运数(幸运数总数小于10005,大家可以自己算一算)
        for (int i=2;i<=t;i++) a[f[i]]=i-1;
       
        int n,k,x,y; scanf("%d%d",&n,&k);
        for (int i=1;i<=n;i++){
                scanf("%d",&x);
                if (a.find(x)==a.end()) y=0;
                else y=a[x];
                s[y]++;
                }         

        int sum=0; g[0][0]=1;
        for (int i=1;i<=t;i++) if (s[i]){
                sum++; g[sum][0]=1;
                for (int j=1;j<=sum;j++){
                        g[sum][j]=(g[sum-1][j]+g[sum-1][j-1]*s[i])%oo;
                        }    //g[sum][j]表示从前sum类幸运数中选出j个的总方法数
                }
       
        ll z=1; c[0]=1;
        for (int i=s[0];i;i--){
                c[i]=z; z=z*i%oo;
                excuild(s[0]-i+1,oo);
                z=z*xx%oo;
                z=(z+oo)%oo;
                }    //c[]表示组合数C(n,k)(k从0取到n)
        ll ans=0;
        for (int i=0;i<=(min(sum,k));i++) ans=(ans+g[sum][i]*c[k-i])%oo;
        printf("%d\n",(ans+oo)%oo);
        return 0;
}

这里C(n,k)如果要分开求的话。最好这样做(这样只调用一次excuild函数)
void excuild(ll a,ll b)
    {
        ll tmp;
        if(b==0){x=1;y=0;return;}
        excuild(b,a%b);
        tmp=x;
        x=y;
        y=tmp - a/b*y;
    }
    
ll c(ll n,ll k)
    {
        ll a=1,b=1,tmp=k,i;
        for(i=0;i<tmp;i++)
        {
            a=a*n%inf;
            b=b*k%inf;
            n--;k--;
        }
        excuild(inf,b);
        a=a*y%inf;
        if(a<0)a+=inf;
        return a;
    }
我就是在这里一直TLE,T死我啦(囧)
搓代码留纪念
int x,y;
void excuild(ll a,ll b)
    {
        ll tmp;
        if(b==0){x=1;y=0;return;}
        excuild(b,a%b);
        tmp=x;
        x=y;
        y=tmp - a/b*y;
    }
ll c(ll a,ll b){
	if(b==0)
		return 1;
	else {
        excuild(b,inf);
		ll xx=x;
		ll tem=(c(a-1,b-1)*a%inf)*xx%inf;
		while(tem<0)
			tem+=inf;
		return tem;
	}
}
由于excuild函数调用太多T了。
 
下面代码是快速幂求逆元的方法
ll mul(ll x,int y)
{
        ll z=1;
        while (y){
                if (y&1) z=(z*x)%inf;
                y/=2; x=(x*x)%inf;
                }
        return z;
}

ll z=1; c[0]=1;
for (int i=n;i;i--){
       c[i]=z; z=z*i%inf;
       z=z*mul(n-i+1,inf-2)%inf;
       z=(z+inf)%inf;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值