BZOJ 2844 albus就是要第一个出场

本文探讨了一种使用线性基数据结构解决特定异或排序问题的方法,通过构建序列A的所有子集的异或值并对其进行排序,找出特定数值在排序后的序列中的首次出现位置。文章详细介绍了线性基的构建、操作以及如何利用它来查找第k小的异或值和确定任意数值的排名,提供了一个C++实现示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://www.lydsy.com/JudgeOnline/problem.php?id=2844

已知一个长度为n的正整数序列A(下标从1开始), 令 S = { x | 1 <= x <= n }, S 的幂集2^S定义为S 所有子

集构成的集合。定义映射 f : 2^S -> Zf(空集) = 0f(T) = XOR A[t] , 对于一切t属于T现在albus把2^S中每个集

合的f值计算出来, 从小到大排成一行, 记为序列B(下标从1开始)。 给定一个数, 那么这个数在序列B中第1

次出现时的下标是多少呢?

Input

第一行一个数n, 为序列A的长度。接下来一行n个数, 为序列A, 用空格隔开。最后一个数Q, 为给定的数.

Output

共一行, 一个整数, 为Q在序列B中第一次出现时的下标模10086的值.

Sample Input

3 1 2 3 1

Sample Output

3 样例解释: N = 3, A = [1 2 3] S = {1, 2, 3} 2^S = {空, {1}, {2}, {3}, {1, 2}, {1, 3}, {2, 3}, {1, 2, 3}} f(空) = 0 f({1}) = 1 f({2}) = 2 f({3}) = 3 f({1, 2}) = 1 xor 2 = 3 f({1, 3}) = 1 xor 3 = 2 f({2, 3}) = 2 xor 3 = 1 f({1, 2, 3}) = 0 所以 B = [0, 0, 1, 1, 2, 2, 3, 3]

Hint

数据范围:

1 <= N <= 10,0000

其他所有输入均不超过10^9

思路:跟这道题学了一个新操作:查一个值是异或第几小。其实也就是查异或第k小的逆过程。然后这道题其实是有规律的,但是我不会证明。证明可以看这个博客:
https://blog.sengxian.com/algorithms/linear-basis
这里只说一下结论:

每个数出现的次数都相等,每个数出现的次数是: 2 n − ∣ B ∣ 其 中 B 是 输 入 的 数 的 一 个 线 性 基 2^{n-|\mathfrak{B}|}\quad其中\mathfrak{B}是输入的数的一个线性基 2nBB线

#include<bits/stdc++.h>
#define ll long long
#define MAXN 10005
#define MOD 10086
using namespace std;
int t,n,q;
ll k,tmp;

struct L_B
{
	ll b[65],p[65],gx[65],base[65];
	int cnt,flag;
	L_B()
	{
	    memset(gx,0,sizeof(gx));
		memset(p,0,sizeof(p));
		memset(b,0,sizeof(b));
		cnt=flag=0;
        for(int i=0;i<=62;i++)
            base[i]=1ll<<i;
    }
	inline bool insert(ll x)//向线性基中插入元素
	{
		for(int i=62;i>=0;--i)
			if(x&base[i])
			{
				if(b[i])
					x^=b[i];
				else
				{
					b[i]=x;
					return true;
				}
			}
		flag=1;//可异或得到0
		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_min()//求最小异或值
	{
		if(flag)
			return 0;
		for(int i=0;i<=62;++i)
			if(b[i])
				return b[i];
		return 0;
	}
	inline void rebuild()//重构 找第k小的前置步骤
	{
		for(int i=62;i>=1;--i)
			if(b[i])
				for(int j=i-1;j>=0;--j)
					if(b[i]&base[j])
						b[i]^=b[j];
		for(int i=0;i<=62;++i)
			if(b[i])
				p[cnt++]=b[i],gx[cnt-1]=i;
	}
	ll kth(ll k)//找第k小
	{
		if(flag)
			--k;
		if(k==0)
			return 0;
		ll ret=0;
		if(k>=base[cnt])
			return -1;
		for(int i=0;i<=cnt-1;++i)
			if(k&base[i])
				ret^=p[i];
		return ret;
	}
	ll getindex(ll v)//看v是异或和第几小
	{
	    ll ans=0;
	    if(flag)
            ++ans;
        for(int i=cnt-1;i>=0;i--)
            if(v&base[gx[i]])
                ans+=base[i];
        return ans;
	}
	void merge(L_B &n2)//把线性基n2 内的元素 逐一插入到线性基n1 中
    {
        flag|=n2.flag;
        for(int i=0;i<=62;++i)
            if(n2.b[i])
                insert(n2.b[i]);
    }
};

ll qpow(ll a,ll b)
{
    ll t1=1,t2=a;
    while(b)
    {
        if(b&1)
            t1=t1*t2%MOD;
        t2=t2*t2%MOD;
        b>>=1;
    }
    return t1;
}

int main()
{
	L_B lis;
	int n;
	scanf("%d",&n);
	ll tmp;
	for(int i=0;i<n;i++)
    {
        scanf("%lld",&tmp);
        lis.insert(tmp);
    }
    lis.rebuild();
    scanf("%lld",&tmp);
    ll ans=lis.getindex(tmp);
    printf("%lld\n",((ans-1)%MOD*qpow(2,n-lis.cnt)%MOD+1)%MOD);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值