P--运(lucky)

【问题背景】

zhx和妹子们玩数数游戏。

【问题描述】

仅包含4或7的数被称为幸运数。 
一个序列的子序列被定义为从序列中删去若干个数,剩下的数组成的新序列。两个子序列被定义为不同的当且仅当其中的元素在原始序列中的下标的集合不相等。对于一个长度为N的序列,共有 2N 个不同的子序列。(包含一个空序列)。 
一个子序列被称为不幸运的,当且仅当其中不包含两个相同的幸运数。 
对于一个给定序列,求其中长度恰好为K的不幸运子序列的个数,答案mod  109+7 输出。

【输入格式】

第一行两个正整数N,K,表示原始序列的长度和题目中的K。 
接下来一行N个整数ai,表示序列中第i个元素的值。

【输出格式】

仅一个数,表示不幸运子序列的个数。(mod 10^9+7)

【样例输入】

3 2 
1 1 1

4 2 
4 7 4 7

【样例输出】


4

【样例解释】

对于样例1,每个长度为2的子序列都是符合条件的。 
对于样例2,4个不幸运子序列元素下标分别为:{1, 2}, {3, 4}, {1, 4}, {2, 3}。注意下标集{1, 3}对应的子序列不是“不幸运”的,因为它包含两个相同的幸运数4.

【数据范围与规定】

对于50%的数据, 1N16 。 
对于70%的数据, 1N1000,ai10000 。 
对于100%的数据, 1N100000,KN,1ai109

        此题良心,暴力拿高分,不过我们并不打算写暴力……

        注意到幸运数的个数很少,我们可以处理出每个幸运数的个数。

        然后考虑非幸运数字,显然是一个组合数问题,这样总共就有(幸运数种数为tot,非幸运数个数为d):

i=1min(tot,k)Ckidcalc(tot,i)

的方案数了,对于calc数组DP处理:设dp[i][j]=从前i个幸运数中选j个的方案数,因为对于一种幸运数,要么选一个要么不选,因此即有以下dp方程(C[i]=离散后第i种幸运数的个数):

dp[i][j]=dp[i1][j]+dp[i1][j1]C[i]

        注意初值:

dp[i][0]=1(0<=i<=tot)

        此问题解决,综上复杂度O(m^2+n),代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#define M 100005
#define P 1000000007
using namespace std;
inline int read(void){
	int x=0,f=1; char ch=getchar();
	for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1;
	for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+ch-'0',ch=getchar());
	return x*f;
}
const int maxn=2009;
map<int,int>mp;
int fact[M],dp[maxn][maxn];
int A[M],C[M];
inline bool check(int x){
    if(!x) return false;
    while(x){
        int d=x%10; x/=10;
        if(d!=4&&d!=7) return false;
    }
    return true;
}
inline int fast(int a,int b){
	int ans=1;
	for(;b;b>>=1,a=1LL*a*a%P)
		if(b&1) ans=1LL*ans*a%P;
	return ans;
}
int Calc(int n,int m){
    if(n>m) return 0;
    return 1LL*fact[m]*fast(fact[m-n],P-2)%P*fast(fact[n],P-2)%P;
}
inline void Check(int &a,int b){
    a+=b; if(a>P) a%=P;
}
int main(int argc,char const *argv[]){
    int n=read(),k=read();
    int tot=0,ans=0,d=0;
    for(int i=1;i<=n;i++){
        A[i]=read();
        if(check(A[i])) mp[A[i]]++;
        else d++;
    }
    fact[0]=1;
    for(int i=1;i<=n;i++)
        fact[i]=1LL*fact[i-1]*i%P;
    for(map<int,int>::iterator it=mp.begin();it!=mp.end();it++)
        C[++tot]=it->second;
    for(int i=0;i<=tot;i++) dp[i][0]=1;
    for(int i=1;i<=tot;i++)
        for(int j=1;j<=i;j++){
            Check(dp[i][j],dp[i-1][j]);
            Check(dp[i][j],1LL*dp[i-1][j-1]*C[i]%P);
        }
    for(int i=0;i<=min(tot,k);i++)
        ans=(ans+1LL*dp[tot][i]*Calc(k-i,d))%P;
    printf("%d\n",ans);
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值