题目描述
校庆志愿者小Z在休息时间和同学们玩卡牌游戏。一共有n张卡牌,每张卡牌上有一个数Ai,每次可以从中选出k张卡牌。一种选取方案的幸运值为这k张卡牌上数的异或和。小Z想知道所有选取方案的幸运值之和除以998244353的余数。
题目分析
这是一道非常**的题目。
首先,我们知道,xor是二进制运算,所以我们首先想到的就是要把所有数都转换成二进制的数。其次,因为它是位运算,所以我们又会想到把这些二进制数的每一位进行单独处理。
首先我们把所有数的第j位拿出来,那么题目就变成了:有n个0和1,要从中选出k个数异或和,求所有方案的和。因为只有两种数,所以我们把它们分开处理。
我们观察以下算式:
1 xor 0 = 1
0 xor 0 = 0
我们发现其实异或0等于什么都没做,我们就先不管0,来看看1:
1 = 1
1 xor 1 = 0
1 xor 1 xor 1 = 1
1 xor 1 xor 1 xor 1 =0
……
我们发现,当奇数个1异或时,答案是1,偶数个1异或时,答案是0。
所以,我们用i枚举选多少个1,然后我们可以得到以下公式:
但其中组合数很难求,80分我们可以直接暴力用杨辉三角形
100分:
我们知道,组合数有一个公式:
对于其中的阶乘,我们可以预处理,但除法怎么办呢?我们就需要乘它的逆元,逆元也可以预处理(尽管我用的是一些很恶心的方法)。
于是,AC到手!
代码
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
ll a[110000][41];ll g[110000];
ll mymax(ll x,ll y) {return x>y?x:y;}
ll mymin(ll x,ll y) {return x<y?x:y;}
ll xx,yy;
void gcd(ll a,ll b)
{
if(b==0)
{
xx=1;yy=0;
return ;
}
gcd(b,a%b);
ll t=xx;
xx=yy;
yy=t-a/b*yy;
}
ll c(ll x,ll y)
{
ll d=1;
for(ll i=1;i<=x;i++)
{
d=(d*i)%998244353;
}
for(ll i=1;i<=x-y;i++)
{
d=(d*g[i])%998244353;
}
for(ll i=1;i<=y;i++)
{
d=(d*g[i])%998244353;
}
return d;
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(ll i=1;i<=ll(n);i++)
{
gcd(i,998244353);
xx=(xx+998244353)%998244353;
g[i]=xx;
}
memset(a,0,sizeof(a));
for(int i=1;i<=n;i++)
{
ll x;
scanf("%lld",&x);
ll e=x;int len=0;
while(e>0)
{
len++;
a[i][len]=e%2;
e/=2;
}
}
ll d=1,ans=0;
for(int i=1;i<=31;i++)
{
ll sum1=0,sum2=0;
for(int j=1;j<=n;j++)
{
if(a[j][i]==1) sum1++;
else sum2++;
}
ll l=mymax(0,k-sum2),r=mymin(sum1,k);
ll temp=c(sum1,l)*c(sum2,k-l);
for(ll j=l;j<=r;j++)
{
if(j%2==1)
{
ans=(ans+temp*d)%998244353;
}
temp=(((temp*(sum1-j))%998244353)*g[j+1])%998244353;
temp=(((temp*(k-j))%998244353)*g[sum2-(k-j)+1])%998244353;
}
d*=2;
}
printf("%lld\n",ans);
return 0;
}