最近在刷DP,刷了好多水题信心满满,然后做了一场figo出的“陈题”,被虐出了翔。
赛后看了figo的题解,又去standing里看了各神犇的各种神写法,又加入了自己c++的c写法,汇成了自己的代码- -。
话说选择了某一些元素就唯一确定了一个子序列,所以子序列中元素的位置与这个子序列是否合法没关系。
所以我们可以吧原序列分成两个子集,一个全是lucky numbers,剩下的全是unlucky numbers。
那么合法的子序列可以这么构成:在lucky里选a个不同的数,在unlucky里选k-a个数。
那么就涉及到dp的范围了,由于lucky numbers会有重复,所以可以看做是一个多重背包的问题。
设有m个不同的lucky number,s[]代表lucky number出现的次数。
那么问题就转换成了,求从m个物品中选出i个物品有多少种取法.
设dp[i][j]代表从i个物品中选出j个物品的组合数。
dp[i][j]=dp[i-1][j](表示不用第i个物品)+dp[i-1][j-1]*s[i]。
自然,最后的结果是sum(dp[m][i]*C(cnt,k-i)),cnt代表unlucky number的个数。
C(n,m)%mod的方法是照神犇抄的,对数论涉及不深啊。。求大神帮忙解释,,感谢感谢。
代码:
#include<cstdio> #include<iostream> #include<map> #define X 100010 #define mod 1000000007 using namespace std; typedef long long ll; map<int,int> g; map<int,int>::iterator gi; ll f[X]={1},dp[X]={1}; ll mi(ll a,ll b){ ll as=1; while(b){ if(b&1)as=(as*a)%mod; b>>=1;a =(a *a)%mod; } return as; } ll c(ll a,ll b){ if(a<b)return 0; ll as=f[a]; as=(as*mi(f[ b],mod-2))%mod; as=(as*mi(f[a-b],mod-2))%mod; return as; } bool luck(int x){ while(x){ if(x%10!=4&&x%10!=7) return 0; x/=10; } return 1; } int main(){ int i,j,k,n,m,x,cnt; ll as; as=cnt=0; for(i=1;i<X;i++) f[i]=(f[i-1]*i)%mod; scanf("%d%d",&n,&k); for(i=1;i<=n;i++){ scanf("%d",&x); if(luck(x))g[x]++; else cnt++; } m=0; for(gi=g.begin();gi!=g.end();gi++){ m=min(m+1,k); for(i=m;i;i--){ dp[i]+=dp[i-1]*gi->second; while(dp[i]>=mod)dp[i]-=mod; } } for(i=0;i<=m;i++) as=(as+dp[i]*c(cnt,k-i))%mod; printf("%I64d\n",as); return 0; }