题目:
题意:
每个人的每题得分情况分为两种,要么能确定一定能得到或一定不能得到分数,要么就可能得到也可能得不到分数
问在分数前
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;
}