CodeChef LEMOVIE
题意:给你n个数字(下标不同数值相同的数字应当被认为是不同的数字),有n!种排列方式.每种排列方式的价值定义为:第一次出现时比前面的所有数字都大的数值个数.
比如1,2,2,3这个排列中,1,2,3这三个数值第一次出现的时候都比前面的所有数字都大,所以这个排列的价值是3.
1,3,1,2这个排列中,1和3第一次出现的时候比前面的所有数字都大,所以这个排列的价值是2.
30分n<=10,另外30分所有数值互不相同.
n<=10只需要n!枚举,所有数值互不相同的时候,我们可以考虑向序列中从大到小一个一个添加数字,那么新添加的数字必须位于当前序列的最前面才会使序列的价值+1,否则序列的价值不变,定义f[i][j]为放置最大的i个数字且当前价值为j的方案数,那么f[i][j]=f[i-1][j-1]+f[i-1][j]*(i-1)
100分的做法需要我们解决一下数值重复出现的情况.那么还是从大到小考虑每一个数值,只需要考虑新加入的数值是否会对答案贡献1.如果所有新加入的数值前面至少有一个原先存在的较大的数值,那么就不会产生贡献.假如原来有x个较大的数值,现在插入y个大小相同的较小数值,那么总的方案数为C(x+y,y),如果要求这y个数值不能产生贡献,即插入后的序列的第一个数还得是原先x个数中的第一个,那么总的方案数为C(x-1+y,y),产生贡献的方案数用总方案数减一下就可以了.
比HEOI2013SAO简单多了.
#include<cstdio> #include<cstring> typedef long long ll; const ll mod=1000000007; const int maxn=505; ll fac[maxn]; ll C[maxn][maxn]; void init(){ fac[0]=1; for(int i=1;i<maxn;++i)fac[i]=fac[i-1]*i%mod; C[0][0]=1; for(int i=1;i<maxn;++i){ C[i][0]=1; for(int j=1;j<=i;++j){ C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod; } } } int cnt[maxn]; int f[maxn][maxn]; int main(){ init(); int tests;scanf("%d",&tests); while(tests--){ memset(cnt,0,sizeof(cnt));memset(f,0,sizeof(f)); int n,k;scanf("%d%d",&n,&k); int x; for(int i=1;i<=n;++i){ scanf("%d",&x);cnt[x]++; } int tot=0,sum=0; for(int i=200;i>=1;--i){//1<=Ai<=200 if(cnt[i]!=0){ if(tot==0){ ++tot;f[1][1]=fac[cnt[i]]; }else{ ++tot; ll sol0=C[sum+cnt[i]-1][cnt[i]]*fac[cnt[i]]%mod,sol1=(C[sum+cnt[i]][cnt[i]]*fac[cnt[i]]%mod-sol0+mod)%mod;; for(int j=0;j<=tot;++j){ f[tot][j]=(f[tot][j]+f[tot-1][j]*sol0%mod)%mod; if(j>0)f[tot][j]=(f[tot][j]+f[tot-1][j-1]*sol1%mod)%mod; } } sum+=cnt[i]; } } ll ans=0; for(int i=0;i<=k;++i)ans=(ans+f[tot][i])%mod; printf("%lld\n",ans); } return 0; }