这个题其实就是个dp(类似背包),但是一些细节还是让我做了一晚上。
这个题学习了组合数取模(逆元法)
补充知识:逆元的求法
(a/b) mod p=a*(b逆) mod p
b*x=1(mod p) x就是b的逆元
而b逆可以利用扩展欧几里德或欧拉函数求得:
1).扩展欧几里德:b*x+p*y=1 有解,x就是所求
#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; }
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; }
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; }