【算法笔记+SGU275+HDU3949】线性基(XOR,求n个数异或得到的第k小的数)

网上大佬写的博客。。。我以为自己看懂了(假装看懂)。。。结果第二题就被卡住了。。。看不懂了。。

https://blog.sengxian.com/algorithms/linear-basis

https://www.cnblogs.com/vb4896/p/6149022.html

是我水平不够,看起来有点费劲(;´༎ຶД༎ຶ`)智商堪忧

还是视频看起来轻松很多:青涩的讲解,但是能听懂,给up主点赞!

目前的理解:线性基就是通过异或构建行阶梯矩阵,找到最大线性无关的向量组,去解决有关XOR的一些问题

数x对应的二进制为:\left (b_n...b_2b_1b_0\right )_2 bj=0或者1,用p[]存下面矩阵中的数

所谓线性基,就是线性代数里面的概念。一组线性无关的向量便可以作为一组基底,张起一个线性的向量空间,这个基地又称之为线性基。

第一个例子:


a[4]=\left\{7,2,3,4\right\},插入的时候从高位向低位看起

--->插入a[0]=7=0111:a[0]的b_2=0,p[2]=a[0]

矩阵:

\begin{bmatrix} 0&0&0&0\\ 0&1&1&1\\ 0&0&0&0\\ 0&0&0&0 \end{bmatrix}

--->插入a[1]=2=0010:a[1]的b_1=1,p[1]=a[1],b_1后面的位k,若b_k=1,那么p[1]^=p[k](p[k]的b_k=1),消掉p[1]中b_1后的1,让p[1]中只有b_1=1;同理,p[1]上面的行j,如果p[j]中的b_1=1,那么p[j]^=p[1],消掉p[j]中b_1位上的1,保证p[j]中只有b_j=1

矩阵:

\begin{bmatrix} 0&0&0&0\\ 0&1&0&1\\ 0&0&1&0\\ 0&0&0&0 \end{bmatrix}

--->插入a[2]=3=0011,从高位向低位看起,因为b_1=1的位置已经存入数a[1]了,所以为了保证我们要构建的是行阶梯矩阵,a[2]^=p[1],新的a[2]=0001,其中b_0=1,p[0]=a[2],和插入a[1]时相同的做法,用下面的行消去自己,用自己消去上面的行中不应该为1的1:

矩阵:

\begin{bmatrix} 0&0&0&0\\ 0&1&0&0\\ 0&0&1&0\\ 0&0&0&1 \end{bmatrix}

--->插入a[3]=4=0100,b_2=1,但是p[2]已经存入数了,a[3]^=p[2]=0,无法插入

至此,线性基对应的行阶梯矩阵已经构建完了,此外,通过构建过程可以知道最终的矩阵可以转化为如下内容:

\begin{bmatrix} 0\\ a[0]\oplus\ a[1] \oplus\ a[2]\\ a[1]\\ a[1] \oplus\ a[2] \end{bmatrix}

p[2]、p[1]、p[0]都不为0,所以线性基的大小为num=3,a[4]中n=4,num

p[2]、p[1]、p[0]对应的向量是一个最大线性无关的向量组,因为num

注意:这里的p[2]、p[1]、p[0]进行异或相当于其对应的向量进行加法运算(二进制加法,没有进位)。

用新的数组L(或者一个vector<>)存不为0的p[i],若求a[4]所能构成的异或的数中的第k小,k=\left (b_n...b_2b_1b_0\right )_2,新的矩阵有如下对应关系:

\begin{bmatrix} 0&1&0&0\\ 0&0&1&0\\ 0&0&0&1 \end{bmatrix}\begin{bmatrix} b_2\\ b_1\\ b_0 \end{bmatrix}

若求第k=1小,因为num!=n,所以可以异或出0,那么可异或出第1小的数就是0

若求第k=4小,因为num!=n,所以可以异或出0,所以k=k-1=3=0011,b_0=1,b_1=1,那么可异或出第1小的数就是L[0]^L[1]等价于a[2] \oplus\ a[3] \oplus\ a[2] = a[3]

第二个例子:


a[3]=\left\{9,11,5\right\}

--->插入a[0]=9=1001,p[3]=a[3]

矩阵:

\begin{bmatrix} 1&0&0&1\\ 0&0&0&0\\ 0&0&0&0\\ 0&0&0&0 \end{bmatrix}

--->插入a[1]=11=1011,p[3]已经有数了,a[1]^=p[3]=0010,p[1]=a[1],用下面的行消自己,用自己消下面的行(但都莫得消)

矩阵:

\begin{bmatrix} 1&0&0&1\\ 0&0&0&0\\ 0&0&1&0\\ 0&0&0&0 \end{bmatrix}

--->插入a[2]=5=0101,p[2]=a[2],用下面的行消自己,用自己消下面的行(但都莫得消)

矩阵:

\begin{bmatrix} 1&0&0&1\\ 0&1&0&1\\ 0&0&1&0\\ 0&0&0&0 \end{bmatrix}

最终的矩阵相当于:其中线性基的大小num=3,n=3

\begin{bmatrix} a[0]\\ a[2]\\ a[0] \oplus\ a[1]\\ 0 \end{bmatrix}

用新的数组L存不为0的p[i],求a[3]能异或成的第k小,k=\left (b_n...b_2b_1b_0\right )_2:,对应关系如下:

\begin{bmatrix} 1&0&0&1\\ 0&1&0&1\\ 0&0&1&0\end{bmatrix}\begin{bmatrix} b_2\\ b_1\\ b_0 \end{bmatrix}

因为线性基大小num=n,所以不能异或出0

若k=1=0001,b_0=1,所以第1小的数就是L[0]

若k=5=1001,b_0=1,b_3=1,所以第5小的数就是L[0]^L[3]

综上所述:


\triangleright 构建行阶梯矩阵时,若a[i]=\left (b_n...b_2b_1b_0\right )_2,从高位往地位第一个为1的位是j,若p[j]已经存入数了,那么a[i]^=p[j],否则直接p[j]=a[i]

\triangleright 在p[j]插入a[i]后,让p[j]下面的行消去p[j]中j位后面的1,让p[j]消去p[j]上面的行中j位上的1,目的是为了尽量使p[j]中只有j位是1

\triangleright 若n个数构成的线性基的大小num=n,则对应的行阶梯矩阵的秩=n,这n个向量线性无关,那么这个n个数不能通过异或得到0,否则可以得到0

\triangleright 在求n个数通过异或构成的第k小的数时,用新的数组L(或者 vector<>)存那些不为0的p[i]

    \triangleright\triangleright num==n,不能得到0,第k小的数就是k的二进制位上为1的位所对应的L[]值,比如K=1001,那么结果就是L[0]^L[3]

    \triangleright\triangleright num!=n,不能得到0,k=k-1,结果就是k的二进制位上为1的位所对应的L[]值

    \triangleright\triangleright num==n时,这个n个数最多能构成2^n-1个不同的数;否则可以构成2^n个不同的数

\triangleright L[i]^L[j]或者p[i]^p[j] 其实就是a[]上的一些数在异或,只不过目前我们不关心是哪些数在异或(当然也可以通过记录得知),我们只在意能异或成的数的大小

\triangleright 求n个数能构成的最大值,就直接把L[]中的值都异或起来就OK啦!

 

终于写完原理了٩(˃̶͈̀௰˂̶͈́)و总算是懂了线性基了☆〜(ゝ。∂)

一般1≤a[i]≤1e18,最多60位,所以max_base设成60,p[max_base+3],max_base别设置太大了,可能结果会错,我试过(´・Д・)」,就60吧!

【算法笔记+SGU275+HDU3949】线性基(XOR,求n个数异或得到的第k小的数)_第1张图片

模版:


hdoj3949

#include 
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const int max_base=60;
ll t,n,q,x;
ll a[maxn],p[maxn+3];
vector v;
void solve()
{
    v.clear();//清空啊啊啊!!!
    memset(p,0,sizeof(p));//初始化啊啊啊!!!
    for(int i=0;i=0;j--)//从高位向低位
        {
            if(a[i]>>j==0) continue;//该位上为0,对线性基这位没有有贡献
            if(!p[j])//该行没数,直接填入
            {
                p[j]=a[i];
                for(int k=j-1;k>=0;k--)
                    if(p[k] && p[j]>>k&1)
                        p[j]^=p[k];//下面的行消自己
                for(int k=j+1;k<=max_base;k++)
                    if(p[k]>>j&1)
                        p[k]^=p[j];//自己消上面的行
                break;//填入之后结束每一位上的遍历
            }
            else
                a[i]^=p[j];//该行已经有数
        }
    }
    for(int i=0;i<=max_base;i++)
        if(p[i])
            v.push_back(p[i]);

}
ll query(ll x)
{
    if(v.size()!=n) x--;
    if(x==0) return 0;//能得到0,第1小的数是0
    if(x>=(1LL<>i&1)
            ans^=v[i];
    return ans;
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt", "r", stdin);
    scanf("%lld",&t);
    for(int i=1;i<=t;i++)
    {
        printf("Case #%d:\n",i);
        scanf("%lld",&n);
        for(int j=0;j

SGU275:但是好像没法补题,求n个数异或得到的最大值,直接把非0的p[i]异或起来就完事了

#include 
using namespace std;
typedef long long ll;
const int maxn=1e4+5;
const int max_base=60;
ll n;
ll a[maxn],p[maxn+3];
void solve()
{
    for(int i=0;i=0;j--)//从高位向低位
        {
            if(a[i]>>j==0) continue;//该位上为0,对线性基这位没有有贡献
            if(!p[j])//该行没数,直接填入
            {
                p[j]=a[i];
                for(int k=j-1;k>=0;k--)
                    if(p[k] && p[j]>>k&1)
                        p[j]^=p[k];//下面的行消自己
                for(int k=j+1;k<=max_base;k++)
                    if(p[k]>>j&1)
                        p[k]^=p[j];//自己消上面的行
                break;//填入之后结束每一位上的遍历
            }
            else
                a[i]^=p[j];//该行已经有数
        }
    }
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt", "r", stdin);
    ll ans=0;
    scanf("%lld",&n);
    for(int i=0;i

 

你可能感兴趣的:(线性基)