BZOJ 2844: albus就是要第一个出场 高斯消元 线性基

2844: albus就是要第一个出场

Time Limit: 6 Sec  Memory Limit: 128 MB
Submit: 1416  Solved: 598
[Submit][Status][Discuss]

Description

已知一个长度为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



学习了一下线性基

推荐一篇博文[学习笔记]线性基


先说题解

高仿PoPoQQQ一发

题目大意:

给定一个n个数的集合S和一个数x,求x在S的2^n个子集从大到小的异或和序列中最早出现的位置

BJ:理解题意靠样例

首先我们求出线性基

我们会得到一些从大到小排列的数和一堆0 记录0的个数

不考虑0,看前面的数

由于线性基的性质

我们直接贪心从大到小枚举 若当前异或和异或这个值小于Q则取这个数 

(注意^不要写成+或者| 本蒟蒻已经因为这个WA了两道题了)(您还是没有BJ蒟蒻啊 哈哈)

然后我们通过每个数取不取可以得到一个01序列

这个序列就是通过异或可以得到的小于Q的数的数量的二进制

比如线性基是8 4 2 Q=13

取完8和4之后无法法取2 那么得到的01序列就是110 即6

BJ:这个要自己YY一下

然后我们再加上空集的0 

然后考虑后面的0

设有m个0

那么每个数出现的次数为2^m 乘起来后+1就是答案

一个细节就是当Q=0的时候计算小于Q的数的数量时不要算上0

不懂的可以自己测测0


代码(之后有线性基学习笔记):

#include<cmath>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<complex>
#include<iostream>
#include<algorithm>
#include<iomanip>
#include<vector>
#include<string>
#include<bitset>
#include<queue>
#include<set>
#include<map>
using namespace std;

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=10*x+ch-'0';ch=getchar();}
	return x*f;
}
inline void print(int x)
{if(x<0)putchar('-'),x=-x;if(x>=10)print(x/10);putchar('0'+x%10);}

const int N=100100,mod=10086;
int n,m,a[N];

void gauss()
{
	int k=0;
	for(int i,j=1<<30;j;j>>=1)
	{
		for(i=1+k;i<=n;++i)if(a[i]&j)break;//cout<<a[i]<<endl;
		if(i==n+1)continue;
		swap(a[i],a[++k]);
		for(int i=1;i<=n;i++)
		if(i!=k&&(a[i]&j))a[i]^=a[k];
	}
	m=n-k;n=k;//cout<<n<<endl;
}

inline int qpow(int x,int y)
{
	int res=1;
	while(y)
	{
		if(y&1)res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}

int main()
{
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	gauss();int now=0,ans=0;int q=read();//cout<<q<<endl;
	for(int i=1;i<=n;i++)
	if((a[i]^now)<q)now^=a[i],(ans+=qpow(2,n-i))%=mod;//cout<<ans<<endl;
	if(q){++ans;ans%=mod;}ans*=qpow(2,m),ans%mod;ans++;ans%=mod;
	print(ans);puts("");return 0;
}
/*
3
1 2 3
1

3

*/

再粘篇博客

话说BJ可真是个转载狂魔

概述

网上好像没有什么关于线性基的资料…

定义

设数集 T 的值域范围为 [1,2n1] 。 
T 的线性基是 T 的一个子集 A={a1,a2,a3,...,an} 。 
A 中元素互相xor所形成的异或集合,等价于原数集 T 的元素互相xor形成的异或集合。 
可以理解为将原数集进行了压缩。

性质

1.设线性基的异或集合中不存在 0 。 
2.线性基的异或集合中每个元素的异或方案唯一,其实这个跟性质1是等价的。 
3.线性基二进制最高位互不相同。 
4.如果线性基是满的,它的异或集合为 [1,2n1] 。 
5.线性基中元素互相异或,异或集合不变。

维护

插入

如果向线性基中插入数 x ,从高位到低位扫描它为 1 的二进制位。 
扫描到第 i 时,如果 ai 不存在,就令 ai=x ,否则 x=xai 。 
x 的结局是,要么被扔进线性基,要么经过一系列操作过后,变成了 0

bool insert(long long val)
{
    for (int i=60;i>=0;i--)
        if (val&(1LL<<i))
        {
            if (!a[i])
            {
                a[i]=val;
                break;
            }
            val^=a[i];
        }
    return val>0;
}

合并

将一个线性基暴力插入另一个线性基即可。

L_B merge(const L_B &n1,const L_B &n2)
{
    L_B ret=n1;
    for (int i=0;i<=60;i++)
        if (n2.d[i])
            ret.insert(n2.d[i]);
    return ret;
}

查询

存在性

如果要查询 x 是否存于异或集合中。 
从高位到低位扫描 x 的为 1 的二进制位。 
扫描到第 i 位的时候 x=xai  
如果中途 x 变为了 0 ,那么表示 x 存于线性基的异或集合中。

最大值

从高位到低位扫描线性基。 
如果异或后可以使得答案变大,就异或到答案中去。

long long query_max()
{
    long long ret=0;
    for (int i=60;i>=0;i--)
        if ((ret^d[i])>ret)
            ret^=d[i];
    return ret;
}

最小值

最小值即为最低位上的线性基。

long long query_min()
{
    for (int i=0;i<=60;i++)
        if (d[i])
            return d[i];
    return 0;
}

k小值

根据性质3。 
我们要将线性基改造成每一位相互独立。 
具体操作就是如果 i<j aj 的第 i 位是 1 ,就将 aj 异或上 ai 。 
经过一系列操作之后,对于二进制的某一位 i 。只有 ai 的这一位是 1 ,其他都是 0 。 
所以查询的时候将k二进制拆分,对于 1 的位,就异或上对应的线性基。 
最终得出的答案就是 k 小值。

void rebuild()
{
    for (int i=60;i>=0;i--)
        for (int j=i-1;j>=0;j--)
            if (d[i]&(1LL<<j))
                d[i]^=d[j];
    for (int i=0;i<=60;i++)
        if (d[i])
            p[cnt++]=d[i];
}
long long kthquery(long long k)
{
    int ret=0;
    if (k>=(1LL<<cnt))
        return -1;
    for (int i=60;i>=0;i--)
        if (k&(1LL<<i))
            ret^=p[i];
    return ret;
}

模板

struct L_B{
    long long d[61],p[61];
    int cnt;
    L_B()
    {
        memset(d,0,sizeof(d));
        memset(p,0,sizeof(p));
        cnt=0;
    }
    bool insert(long long val)
    {
        for (int i=60;i>=0;i--)
            if (val&(1LL<<i))
            {
                if (!d[i])
                {
                    d[i]=val;
                    break;
                }
                val^=d[i];
            }
        return val>0;
    }
    long long query_max()
    {
        long long ret=0;
        for (int i=60;i>=0;i--)
            if ((ret^d[i])>ret)
                ret^=d[i];
        return ret;
    }
    long long query_min()
    {
        for (int i=0;i<=60;i++)
            if (d[i])
                return d[i];
        return 0;
    }
    void rebuild()
    {
        for (int i=60;i>=0;i--)
            for (int j=i-1;j>=0;j--)
                if (d[i]&(1LL<<j))
                    d[i]^=d[j];
        for (int i=0;i<=60;i++)
            if (d[i])
                p[cnt++]=d[i];
    }
    long long kthquery(long long k)
    {
        int ret=0;
        if (k>=(1LL<<cnt))
            return -1;
        for (int i=60;i>=0;i--)
            if (k&(1LL<<i))
                ret^=p[i];
        return ret;
    }
}
L_B merge(const L_B &n1,const L_B &n2)
{
    L_B ret=n1;
    for (int i=60;i>=0;i--)
        if (n2.d[i])
            ret.insert(n1.d[i]);
    return ret;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值