5414. 【NOIP2017提高A组集训10.22】幸运值

题目描述

校庆志愿者小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,然后我们可以得到以下公式:

i=0kCis1Ckis02j1(i=1(mod2)and(i<=s1)) ∑ i = 0 k C s 1 i ∗ C s 0 k − i ∗ 2 j − 1 ( i = 1 ( m o d 2 ) a n d ( i <= s 1 ) )

但其中组合数很难求,80分我们可以直接暴力用杨辉三角形
100分:
我们知道,组合数有一个公式:
Cmn=n!m!(nm)! C n m = n ! m ! ( n − m ) !

对于其中的阶乘,我们可以预处理,但除法怎么办呢?我们就需要乘它的逆元,逆元也可以预处理(尽管我用的是一些很恶心的方法)。
于是,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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值