1. 程式人生 > >[線性基 模板]

[線性基 模板]

網上找到一篇寫的非常棒的文章Orz, 看完之後相信你一定也可以學會並理解
點我

個人理解線性基的關鍵:

  1. 線性基可以有多個,但是每個內部一定都是線性無關的,也就是說對於一個可以用線性基表示的x來說, 其組成方案一定是唯一的, 所以一個線性基的任意子集都不會構造出0.
  2. 構造線性基的核心就是 : 對於一個基 a , b
    { a, b }
    , 我們可以用 a b a ^ b 替換任意一個,且不會影響這個基本身. 有了這個我們才可以很巧妙地構造線性基,且可以在這個基礎上按照一定規則進行一些操作可以讓這個線性基的性質更加的好看

思考

  1. 原集合能夠亦或出0 ? 如果原集合的大小等於線性基的大小, 那麼肯定不能夠亦或出0, 因為線性基的任意子集都不會被亦或出0, 如果不等於, 那麼原陣列中多餘的數都可以被線性基表示, 然後這兩者亦或一定為0
  2. 原集合的亦或第k小 ? 記ai為線性基中最高位的1在第i位的 向量, 可以按照i從大到小的順序,用ai去異或aj (j>i).
    這樣最後得到的重塑後向量組又多了一個優美的性質: 只有ai的第i位是1,其他的第i位都是0. 有了這個性質,就容易證明要求第k小的異或和,只要把k二進位制拆分,線性基中第j位是1就異或第j個線性基中的向量。正確性,我們腦補一下, 因為這樣 重塑之後, 假設線性基的大小為sz, 那麼從最低位到最高位就可以完全控制最後亦或值的大小,那麼他可以組合出方案數為(1 << sz) - 1 個,並且按照二進位制列舉的方式越來越大.還是無法說明白,QAQ

HDU-3949 亦或第k小

Code

/***********************************************
Author        :lzs
Created Time  :2018年10月19日 星期五 10時47分05秒
File Name     :xjj.cpp
************************************************/

#include <bits/stdc++.h>
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <string>
#include <math.h>
#include <stack>
#include <vector>
#include <queue>
#include <set>
#include <map>
using namespace std;
#define rep(i, l, r) for(int i = l; i < r; i++)
#define per(i, r, l) for(int i = r; i >= l; i--)
#define dbgln(...) cerr<<"["<<#__VA_ARGS__":"<<(__VA_ARGS__)<<"]"<<"\n"
#define dbg(...) cerr<<"["<<#__VA_ARGS__":"<<(__VA_ARGS__)<<"]"


typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>pii;

const int N = (int) 1e5 + 11;
const int M = (int) 1e6 + 11;
const int MOD = (int) 1e9 + 7;
const int INF = (int) 0x3f3f3f3f;
const ll INFF = (ll) 0x3f3f3f3f3f3f3f3f;
/*-----------------------------------------------------------*/

int n;
ll xjj[70], xj[70];
bool insert(ll a){ // 如果最後a為0,那麼表示 a可以用線性基表示
	for(ll i = 62; i >= 0; i--){
		if(a >> i & 1) {
			if(!xjj[i]) {
				xjj[i] = a;
				break;
			}
			a ^= xjj[i];
		}
	}
	return a == 0;
}
ll getmx(ll x = 0){
	// 不傳引數,表示求原集合中的亦或最大值
	for(int i = 62; i >= 0; i--){
		if(x < ( x ^ xjj[i])) x = x ^ xjj[i];
	}
	return x;
}
// 原集合中的亦或最小值 就是線性基中的最低位的值
int sz;
void rebuild(){
	sz = 0;
	for(int i = 62; i >= 0; i--){
		for(int j = i - 1; j >= 0; j--){
			if(xjj[i] >> j & 1) xjj[i] ^= xjj[j];
		}
	}
	for(int i = 0; i <= 62; i++) if(xjj[i]) xj[sz++] = xjj[i];
}
ll query_kth(ll k){
	if(k >= (1ll << sz)) return -1; ll ans = 0;
	for(int i = 62; i >= 0; i--){ // 順序列舉和倒序列舉都不影響
		if(k >> i & 1) ans ^= xj[i];
	}
	return ans;
} 
int main(){
//	cout << (1ll << 62) <<"\n";	
	int T; scanf("%d", &T);
	for(int cas = 1; cas <= T; cas++){
		scanf("%d" ,&n);
		memset(xjj, 0, sizeof(xjj));
		for(int i = 1; i <= n; i++) {
			static ll t; scanf("%lld", &t);
			insert(t);
		}
		rebuild();
		int q; scanf("%d" ,&q);
		printf("Case #%d:\n", cas);
		while(q--){
			static ll k; scanf("%lld" ,&k);
			if(n != sz) k--; // 如果線性基的大小 等於 原陣列大小,則0是不能夠被亦或出來的
			printf("%lld\n", query_kth(k));
		}
	}
	return 0;
}