1. 程式人生 > >關於整型異或的線性基

關於整型異或的線性基

  • 線性基

         \ \ \ \ \ \ \, 基( b a s

i s \tt basis )是線性代數中的一個概念,它是描述、刻畫向量空間的基本工具。而在現行的 OI 題目中,通常在利用基在異或空間中的一些特殊性質來解決題目,而這一類題目所涉及的知識點被稱作線性基。

        

\ \ \ \ \ \ \, 具體證明先不談,【給個連結】,下面主要講關於整型異或的線性基的程式碼實現和操作。

         \ \ \ \ \ \ \,

    簡單來說,線性基是解決一系列整形集合異或值的輕量資料結構,主要思想在於貪心,空間複雜度很低,只有 log n \log n 的級別( n n 為元素大小上界):

struct Linear_Base{
  long long a[Lim+10];
}LB;

         \ \ \ \ \ \ \, 他支援的操作有( n n 為元素大小上界):

  1. 向集合中插入一個整形元素( O ( log n ) O(\log n)

  2. 詢問集合的中元素是否可以相互異或出某個整形( O ( log n ) O(\log n)

  3. 詢問集合的中元素與某個整形可以相互異或出的最大值( O ( log n ) O(\log n)

  4. 詢問集合的中元素與某個整形可以相互異或出的最小值( O ( log n ) O(\log n)

  5. 詢問集合的中元素可以相互異或出的第 k k 大值( O ( log 2 n ) O(\log^2 n)

  6. 合併兩個集合(線性基)( O ( log 2 n ) O(\log^2 n)


  • 插入

         \ \ \ \ \ \ \, 考慮按位異或其實只與每一個位數有關,並且要這個位數為 1 1 的時候才會造成貢獻。問題是如何用 log n \log n 的空間來描述整個集合的異或集合,我們的想法是:

  • 如果這個數前面高數位的值可以被已有集合表達,那麼我們用插入的數異或掉這個可以表達的數,就可以把插入的數化為低位。

  • 如果這個數前面高數位的值不能被已有集合表達,那麼我們就順勢插入集合,然後彈出。

         \ \ \ \ \ \ \, 這樣我們就可以保證,集合的異或值都可以表達出來,沒有遺漏,程式碼如下:

void insert(long long x){
  for(int i=Lim;i>=0;i--)if(x&(1ll<<i)){
    if(!a[i]){a[i]=x;return;}
    else x^=a[i];
  }
}

  • 查詢是否存在

       &ThinSpace; \ \ \ \ \ \ \, 在插入完成後,線性基的每一一個位置存放的都是這個集合可以通過異或表達的元素,並且最高位就是這個數位,於是查詢存在就邊成了:

  • 如果詢問的數在這個數位為 1 1 ,那麼要表達詢問的數的一個元素,肯定是這個位數上的元素,於是我們拿詢問的數去異或他,詢問的數將會成功減少至少一個數位。

  • 如果詢問的數在這個數位為 0 0 ,那麼要表達詢問的數的元素肯定沒有這個位數上的元素,不然後面再怎麼異或,這個位數就消不掉了,所以直接跳過這種位數。

       &ThinSpace; \ \ \ \ \ \ \, 當然了,要是最後把要查詢的這個數都異或為 0 0 了,這個數當然就存在了,若是異或到後面的位數上的元素都是空的了還沒有歸 0 0 ,那麼肯定就不存在了,程式碼如下:

bool check(long long x){
  for(int i=Lim;i>=0;i--)if(x&(1ll<<i)){
    if(!a[i])break;
    x^=a[i];
  }
  return x>0;
}

  • 查詢最大

       &ThinSpace; \ \ \ \ \ \ \, 這個比較好想,我們依然從高位到地位列舉,若是詢問的數這一個數位的元素異或起來變大了,那麼就異或起來,最後便可以得到答案:

long long querymax(long long res){
  for(int i=Lim;i>=0;i--)
  if((res^a[i])>res)res^=a[i];
  return res;
}

  • 查詢最小

       &ThinSpace; \ \ \ \ \ \ \, 查詢最小的話,我們的策略就需要改變一下了,應該是儘量把為搞數位降成低數位,那麼我們的操作和插入的操作其實差不多:

long long querymax(long long res){
  for(int i=Lim;i>=0;i--)
  if(res&(1ll<<i))res^=a[i];
  return res;
}

  • 查詢組合第 k k

       &ThinSpace; \ \ \ \ \ \ \, 這個是最複雜的操作了,簡單說一下就好了,主要記板子,主要的想法是我們需要將每一位數的元素後面的位數也消掉,那麼我們每一數位的元素就差不多變成了 2 i 2^i ,然後把 k k 位數拆分就好了:

long long querykth(int k){
  long long tmp[Lim+10],res=0,cnt=0;
  for(int i=0;i<=Lim;i++){
    for(int j=i-1;j>=0;j--)if(a[i]&(1ll<<j))a[i]^=a[j];
    if(a[i])tmp[cnt++]=a[i];
  }
  for(int i=0;i<cnt;i++)if(k&(1ll<<i))res^=tmp[i];
  return res;
}

  • 合併

       &ThinSpace; \ \ \ \ \ \ \, 合併的話,就是把一個線性基裡面整合的元素再插入另一個就好了唄:

void merge(const Linear_Base &other)
{for(int i=0;i<=Lim;i++) insert(other.a[i]);}

  • 完整模板程式碼:

struct Linear_Base{
  long long a[Lim+10];
  void insert(long long x){
    for(int i=Lim;i>=0;i--)if(x&(1ll<<i)){
      if(!a[i]){a[i]=x;return;}
      else x^=a[i];
    }
  }
  bool check(long long x){
    for(int i=Lim;i>=0;i--)if(x&(1ll<<i)){
      if(!a[i])break;
      x^=a[i];
    }
    return x>0;
  }
  long long querymax(long long res){
    for(int i=Lim;i>=0;i--)if((res^a[i])>res)res^=a[i];
    return res;
  }
  long long querymin(long long res){
    for(int i=Lim;i>=0;i--)if(res&(1ll<<i))res^=a[i];
    return res;
  }
  long long querykth(int k){
    long long tmp[Lim+10],res=0,cnt=0;
    for(int i=0;i<=Lim;i++){
      for(int j=i-1;j>=0;j--)if(a[i]&(1ll<<j))a[i]^=a[j];
      if(a[i])tmp[cnt++]=a[i];
    }
    for(int i=0;i<cnt;i++)if(k&(1ll<<i))res^=tmp[i];
    return res;
  }
  void merge(const Linear_Base &other)
  {for(int i=0;i<=Lim;i++) insert(other.a[i]);}
}LB;