1. 程式人生 > >BZOJ 2844: albus就是要第一個出場 高斯消元 線性基

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<jaj的第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;
}