题目:
题意:
每个人的每题得分情况分为两种,要么能确定一定能得到或一定不能得到分数,要么就可能得到也可能得不到分数
问在分数前 s s s名中选出 t t t人的方案数有多少
分析:
转换一下题意,其实是在问有多少个大小为 t t t的集合能在 s s s当中
对于 ∀ x ϵ t \forall x\ \epsilon\ t ∀x ϵ t,为了选到 t a ta ta,我们需要让 t a ta ta的分数最大化,而对于不在 t t t中的人的分数我们希望最小化,这样就是最有可能让 t ϵ s t\ \epsilon\ s t ϵ s
接下来我们就将分数的可能性转化为每人记录两个值:分数最大与最小值
设 f i , j , k f_{i,j,k} fi,j,k表示选到第 i i i人, t t t集合中已经有 j j j人, s s s集合中已经有 k k k人
我们对于分数最大值进行降序排序,枚举一个 i j ij ij表示 t t t中已经有 i j ij ij
这样一来首先位置在 i j ij ij之后的数就没有考虑的必要了
位置在 i j ij ij之前的数,要选的话就考虑分数最大值,但因为我们是按照最大值降序排序的,所以直接做贡献即可;不选的话就考虑分数最小值,如果 t a ta ta的最小值还是大于 i j ij ij的最大值,就说明 t a ta ta一定是不在 t t t中却在 s s s的一个数
这样一直做到 i j − 1 ij-1 ij−1,显然为了凑够 t t t个数,我们的答案需要统计 f i j − 1 , t − 1 , k f_{ij-1,t-1,k} fij−1,t−1,k,而 k k k的值是多少已经不重要了,因为我们关心的只是 t t t里的数,至于 s s s的数对当前集合的贡献不会造成任何影响,所以我们只需保证 k ⩽ s − t k\leqslant s-t k⩽s−t,这是因为在集合中的 t t t个数也必定在 s s s当中
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<algorithm>
#include<queue>
#define LL long long
using namespace std;
inline LL read()
{
LL s=0,f=1; char c=getchar();
while(c<'0'||c>'9') {
if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') {
s=s*10+c-'0';c=getchar();}
return s*f;
}
LL xa[55],ia[55],a[55],p[55];
bool cmp(LL i,LL j) {
return xa[i]>xa[j];}
LL f[55][55][55];
int main()
{
freopen("ctsc.in","r",stdin);
freopen("ctsc.out","w",stdout);
LL n=read();
for(LL i=1;i<=n;i++) a[i]=read();
LL m=read();char c[55];
for(LL i=1;i<=m;i++)
{
p[i]=i;
scanf("%s",&c);
for(LL j=1;j<=n;j++)
{
if(a[j]>0) xa[i]+=a[j]*(c[j-1]=='Y'?1:0),ia[i]+=a[j]*(c[j-1]=='Y'?1:0);
else xa[i]+=(-a[j])*(c[j-1]=='Y'?1:0);
}
xa[i]=xa[i]*100+m-i;
ia[i]=ia[i]*100+m-i;
}
sort(p+1,p+1+m,cmp);
LL s=read(),t=read(),ans=0;
for(LL i=1;i<=m;i++)
{
LL lim=s-t+1;
memset(f,0,sizeof(f));
f[0][0][0]=1;
for(LL j=1;j<i;j++)
for(LL c=0;c<t;c++)
for(LL b=0;b<lim;b++)
{
if(b&&ia[p[j]]>xa[p[i]]) f[j][c][b]+=f[j-1][c][b-1];
else if(ia[p[j]]<xa[p[i]]) f[j][c][b]+=f[j-1][c][b];
if(c) f[j][c][b]+=f[j-1][c-1][b];
}
for(LL b=0;b<lim;b++) ans+=f[i-1][t-1][b];
}
cout<<ans;
return 0;
}