BZOJ 2844 | HYSBZ - 2844albus就是要第一个出场——线性基

【题目描述】
BZOJ 2844 | HYSBZ - 2844albus
【题目分析】
题目的意思大概是给一个数列,他有2n个子集,每个子集的元素的异或和构成新的一个数列,排序后问数字Q在这个序列里面的下标。
假如题目是求所有元素的异或和构成一个集合就好弄了,我们可以根据求第K大反过来求他在集合里面的下标。

int find_ans(int q)
    {
        int num=0; int ans=0;
        vector<int> p;
        for(int i=0;i<=62;i++)
        {
            if(b[i])
            {
                p.push_back(i);	//如果这个位置有数字,就把这个位置记下来
                num++;
            }
        }
        for(int i=num-1;i>=0;i--)
        {
            if(q>>p[i]&1)	//如果这个数字在这一位有数字,那么排名就向前移动1<<i位(在0的基础上)
            {
                ans+=1<<i;	//这里的i是指这一位前面有多少个高位,也就是说如果这一位是0的数字个数,加上它就是在0的基础上向前移动的排名
            }
        }
        //printf("test: ans=%d num=%d p[0]=%d\n",ans,num,p[0]);
        return ans+1;//因为前面还有一个0,所以要+1
    }

可是我们知道这样肯定是求不出答案的,还有好多重复的数字,我们不妨来分析一下这些重复 的数字。对于线性基异或出来的一个数字x,所有和他重复的数字肯定是由那些不包含在线性基里面的数字帮忙异或出来的。
原因是线性基的两个性质:
1.线性基异或出来的数字不相同。
2.线性基无法异或出0。
而这里其他重复的数字相当于线性基的一部分数字异或出 x后再异或一些0(异或0不会改变数值),而通过上面的分析我们知道这些0只能是那些没有进入线性基的数字和一部分进入线性基的 数字异或出来的(当然也可能不包含线性基里面的数字,但肯定不是纯线性基的数字),那么我们能异或出多少个0呢?对于线性基外的每一个数字我们都可以用线性基里面的一部分数字异或出来,所以线性基外的每一个数字配合线性基里的一部分数字都能异或出0,可是不同的0有多少个?我们不妨将线性基外的数字放入一个集合,这个集合的所有子集配合他们相应的能够异或出他们的线性基的数字就能异或出不同的0。而这个子集的个数很好求,2n-num个,n-num为线性基外的数字个数。
所以,我们要做的就是算出q-1前面有多少个数字,然后乘2n-num,再+1就是第一个q的位置。
我们不妨借用上面的程序,上面算出的是在不重复的集合中q的下标,那前面就是(下标-1)*2n-num+1就是答案了。
【AC代码】

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<climits>
#include<cstdlib>
#include<cmath>

using namespace std;

typedef long long ll;

const int MAXN=100005;
const int MOD=10086;
int n,q,tmp;
ll quick_pow(ll a,ll b);	//用快速幂算2^n-num
struct L_B
{
	ll b[65],p[65];
	int cnt,flag;
	L_B()
	{
		memset(p,0,sizeof(p));
		memset(b,0,sizeof(b));
		cnt=flag=0;
	}
	void clear()
	{
		memset(p,0,sizeof(p));
		memset(b,0,sizeof(b));
		cnt=flag=0;
	}
	inline bool insert(ll x)
	{
		for(int i=62;i>=0;--i)
			if(x&(1ll<<i))
			{
				if(b[i])
					x^=b[i];
				else
				{
					b[i]=x;
					return true;
				}
			}
		flag=1;
		return false;
	}
	ll get_max()
	{
		ll ret = 0;
		for(int i=62;i>=0;--i)
			if((ret^b[i])>ret)
				ret^=b[i];
		return ret;
	}
	ll get_max(ll initval)
	{
		ll ret = initval;
		for(int i=62;i>=0;--i)
			if((ret^b[i])>ret)
				ret^=b[i];
		return ret;
	}
	ll get_min()
	{
		if(flag)
			return 0;
		for(int i=0;i<=62;++i)
			if(b[i])
				return b[i];
		return 0;
	}
	inline void rebuild()
	{
		for(int i=1;i<=62;++i)
			if(b[i])
				for(int j=0;j<i;++j)
					if(b[i]&(1ll<<j))
						b[i]^=b[j];
		for(int i=0;i<=62;++i)
			if(b[i])
				p[cnt++]=b[i];
	}
	ll kth(ll k)
	{
		if(flag)
			--k;
		if(k==0)
			return 0;
		ll ret = 0;
		if(k>=(1ll<<cnt))
			return -1;
		for(int i=0;i<=cnt-1;++i)
			if(k&(1ll<<i))
				ret^=p[i];
		return ret;
	}
    int find_ans(int q)
    {
        int num=0; int ans=0;
        vector<int> p;
        for(int i=0;i<=62;i++)
        {
            if(b[i])
            {
                p.push_back(i);
                num++;
            }
        }
        for(int i=num-1;i>=0;i--)
        {
            if(q>>p[i]&1)
            {
                ans+=1<<i;
            }
        }
        //printf("test: ans=%d num=%d p[0]=%d\n",ans,num,p[0]);
        return (ans*quick_pow(2,n-num))%MOD+1;//ans本来是要+1再-1的,这里就省略了(我之前看其他博客他们好像都没有讲这个然后我就好久都不能理解,emmm,还是太菜了)
    }
};

ll quick_pow(ll a,ll b)
{
    ll ret=1;
    while(b)
    {
        if(b%2) ret=(ret*a)%MOD;
        a=a*a%MOD;
        b>>=1;
    }
    return ret;
}

int main()
{
    
    L_B lis;
    int ans=0;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
    {
        scanf("%d",&tmp);
        lis.insert(tmp);
    }
    scanf("%d",&q);
    printf("%d",lis.find_ans(q)%MOD);
    
    //printf("%lld",quick_pow(2,5));
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值