[BZOJ 2844]albus就是要第一个出场

Description
已知一个长度为n的正整数序列A(下标从1开始), 令 S = { x | 1 <= x <= n }, S 的幂集2S定义为S 所有子
集构成的集合。定义映射 f : 2S -> Z f Z_f Zf(空集) = 0f(T) = XOR A[t] , 对于一切t属于T,现在albus把2S中每个集合的f值计算出来, 从小到大排成一行, 记为序列B(下标从1开始)。 给定一个数, 那么这个数在序列B中第1次出现时的下标是多少呢?
Input
第一行一个数n, 为序列A的长度。接下来一行n个数, 为序列A, 用空格隔开。最后一个数Q, 为给定的数.
Output
共一行, 一个整数, 为Q在序列B中第一次出现时的下标模10086的值.

大意
给定包含n个元素的数组 a [ ] a[] a[],显然 a [ ] a[] a[]共有2n个子集(包括空集及其自身),将这2n个子集的异或值从小到大排序,排序后的下标从1开始,求给定的输入Q第一次出现时的下标mod 10086的值。

思路
我们知道对于一个其中有 r r r个元素的线性基,任选其中的子集(包括空集)我们能组合出2r个不同的异或值。但是只有这个结论并不够
同时若一个集合内有 n n n个元素而其对应的线性基有 r r r个元素,那么对于每个可以由线性基中的元素异或出的异或值,在原集合中都有2n-r中选法能够构造出这个异或值。
证明如下:

  • 对于任意一个可构造的异或值 x o r i xor_i xori,先从线性基中选取一组元素构造这个异或值(显然选法唯一),再考虑选取不在线性基中的 n − r n-r nr个元素,则有2n-r种选法,而后,对于每一个不在线性基中的元素 a i a_i ai,均可在线性基中选出对应的一组元素与 a i a_i ai的异或值为0(选法唯一)。而线性基中的一个元素若被选取(异或)了两次则相当于没有被选取,故对于2n-r种选法在线性基中都有唯一对应的选取方法可以构造出目标异或值 x o r i xor_i xori

所以只需要计算出线性基中的元素个数 r r r,再找出Q是线性基中第 k k k小的可构造异或值,那么答案就是 ( 2 n − r ∗ ( k − 1 ) + 1 ) m o d 10086 (2^{n-r}*(k-1)+1) mod 10086 (2nr(k1)+1)mod10086了。
但是不用快速幂可能会TLE
考虑可以选取空集,所以0也是可构造的异或值

AC代码
之前数组开小了一直RE

#include<stdio.h>
#include<algorithm>
#include<vector>
#include<map>
#include<queue>
#include<stack>
#include<stdlib.h>
#include<string.h>
#define MOD 10086
using namespace std;
typedef long long ll;

typedef struct L_B
{
#define NUM 35	
	private:
	ll b[NUM+5],p[NUM+5],flag,cnt;
	public:
	L_B()
	{
		for(int i=0;i<NUM+5;i++)b[i]=p[i]=0;cnt=0;flag=1;
	}
	void insert(ll now)//插入
	{
		for(int i=NUM;i>=0;--i)
		{
			if(now&(1ll<<i))
			if(this->b[i])now^=this->b[i];
			else 
			{
				this->b[i]=now;return;
			}
		}
		this->flag=1;
		return;
	}
	ll kth(ll k)//取第k大 
	{
		if(this->flag)--k;
		if(!k)return 0;
		ll ret=0;
		if(k>=(1ll<<this->cnt))return -1;
		for(int i=0;i<=this->cnt-1;++i)
			if(k&(1ll<<i))
			ret^=p[i];
		return ret;
	}
	void rebuild()//重构 
	{
		for(int i=1;i<=NUM;i++)
		if(this->b[i])
		{
			for(int j=0;j<i;j++)
			{
				if(this->b[i]&(1ll<<j))
				this->b[i]^=this->b[j];
			}
		}
		for(int i=0;i<=NUM;i++)
		{	
			if(this->b[i])p[(this->cnt)++]=this->b[i];
		}
		return;
	}
	ll get_max(ll now)//取最大值 
	{
		ll ret=now;	
		for(int i=NUM;i+1;--i)
		{
			if((ret^this->b[i])>ret)ret^=this->b[i];
		}
		return ret;
	}
	ll get_min()//取最小值 
	{
		if(this->flag)
			return 0;
		for(int i=0;i<=NUM;i++)
		{
			if(this->b[i])return this->b[i];
		}
		return 0;
	}
	void merge(L_B n2)//合并线性基
	{
		for(int i = 0;i <= NUM;++i)
			if(n2.b[i])
				this->insert(n2.b[i]);
		this->flag=this->flag|n2.flag;
		return;
	}
	ll find(ll target)
	{
		ll ans=0;
		for(ll i=cnt;i+1;i--)
		{
			if((target^p[i])<target)
			{
				//target^=p[i];
				ans+=1ll<<i;
			}
		}
		return ans+this->flag;
	}
	ll get_cnt(){return this->cnt;}
#undef NUM
} L_B;
long long fast_pow(long long target,long long p);
L_B B;
ll n,q,qu,t,a[100005],Case=0,k;
int main()
{

	scanf("%lld",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)B.insert(a[i]);
	B.rebuild();
	scanf("%lld",&k);
	ll t=fast_pow(2,n-B.get_cnt());
	ll ans=B.find(k)-1;
	ans*=t;ans++;ans%=MOD;
	printf("%lld\n",ans);
	return 0;
}
long long fast_pow(long long target,long long p)
{
	long long z[50];
	z[1]=target;
	for(int i=2;i<50;i++)
	{
		z[i]=z[i-1]*z[i-1];z[i]%=MOD;
	}
	long long ans=1;
	for(int i=1;p;i++)
	{
		if(p%2) ans=(ans*z[i])%MOD;p/=2;
	}
	return ans;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值