题目大意
校庆志愿者小Z在休息时间和同学们玩卡牌游戏。一共有n张卡牌,每张卡牌上有一个数Ai,每次可以从中选出k张卡牌。一种选取方案的幸运值为这k张卡牌上数的异或和。小Z想知道所有选取方案的幸运值之和除以998244353的余数。
题解
对于这道题目,我们可以用一个经典套路——拆位,我们知道两个数异或,如果它们在二进制下某一位相同,则异或后,在二进制下,这一位为0,否则为1,那我们分开考虑这n个数某一位的贡献,这一位有贡献,当且仅当选出来的这k个数(在二进制下)的这一位一共有奇数个1,因为这样子异或之后才是1,进而才对答案有贡献。那么我们只需要枚举一下选多少个一即可。(设此为所有的数的的和为sum),此位为第i位。
那么这一位的贡献即为
∑min(sum,k)j=0,j为奇数CjsumCk−jn−sum∗2i
∑
j
=
0
,
j
为
奇
数
m
i
n
(
s
u
m
,
k
)
C
s
u
m
j
C
n
−
s
u
m
k
−
j
∗
2
i
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const long long mo=998244353;
const int MAXN=100000+5;
long long a[MAXN],inv[MAXN],fact[MAXN];
long long power(long long a,long long b)
{
long long t=1,y=a%mo;
while (b)
{
if (b&1)
t=t*y%mo;
y=y*y%mo;
b>>=1;
}
return t;
}
int main()
{
freopen("card.in","r",stdin);
freopen("card.out","w",stdout);
int n,k;
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++)
scanf("%d",&a[i]);
fact[0]=inv[0]=1;
for (int i=1;i<=n;i++)
{
fact[i]=fact[i-1]*i%mo;
inv[i]=power(fact[i],mo-2);
}
int sum;
long long ans=0;
for (int i=0;i<=30;i++)
{
sum=0;
for (int j=1;j<=n;j++)
sum+=((a[j]>>i)&1);
for (int j=1;j<=min(k,sum);j++)
if (k-j<=n-sum&&j&1)
ans=(ans+fact[sum]*inv[j]%mo*inv[sum-j]%mo*fact[n-sum]%mo*inv[k-j]%mo*inv[n-sum-k+j]%mo*(1ll<<i)%mo)%mo;
}
printf("%lld",ans);
return 0;
}
小结
虽说这道题还是比较简单,但这个套路还是值得思考。