jzoj5414(幸运值)

题目大意

校庆志愿者小Z在休息时间和同学们玩卡牌游戏。一共有n张卡牌,每张卡牌上有一个数Ai,每次可以从中选出k张卡牌。一种选取方案的幸运值为这k张卡牌上数的异或和。小Z想知道所有选取方案的幸运值之和除以998244353的余数。

题解

对于这道题目,我们可以用一个经典套路——拆位,我们知道两个数异或,如果它们在二进制下某一位相同,则异或后,在二进制下,这一位为0,否则为1,那我们分开考虑这n个数某一位的贡献,这一位有贡献,当且仅当选出来的这k个数(在二进制下)的这一位一共有奇数个1,因为这样子异或之后才是1,进而才对答案有贡献。那么我们只需要枚举一下选多少个一即可。(设此为所有的数的的和为sum),此位为第i位。
那么这一位的贡献即为
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;
}

小结

虽说这道题还是比较简单,但这个套路还是值得思考。

猜你喜欢

转载自blog.csdn.net/ganjingxian/article/details/79146489