线性基

线性基是一个集合,从原集合取任意多个值异或的结果都可以在这个线性基的集合中选取一些数异或得到,也就是说在异或意义下线性基与原集合等价。
线性基的求法:
首先引入一条性质,对于一个集合来说,任意两个数的异或值代替其中某一个数的集合与原集合在异或意义下等价。若需要原来被替换的那个数,就只需要用新数异或另一个数即可。
重复利用这条性质,我们就可以使得集合中每个数的二进制最高位均不相同(若相同则异或形成新值)。这样操作下来就得到这个集合的线性基了,大小只有log(元素最大值)。

#include <iostream>
using namespace std;

typedef long long ll;

ll p[70];
int flag = 0;

bool insert(ll x)   //将元素插入线性基中
{
	for (int i = 62; i >= 0; i--)   //从最高位往下 
	{
		if( x & ((ll)1<<i) )   //若该位为1 
		{
			if( p[i] == 0 )    //线性基的对应位为0就直接填入 
			{
				p[i] = x;
				return true;   //插入成功 
			}else x ^= p[i];   //异或线性基的该位将这个数的该位的1删去 
		}
	}
	flag = 1;    //元素无法插入则说明可以异或出0 
	return false;
}

ll get_max()    //找最大的异或值 
{
	ll ans = 0;
	for (int i = 62; i >= 0; i--)   //从高位开始枚举 
	{
		if( (ans ^ p[i] ) > ans ) ans ^= p[i];  
		//异或上变大可以说明原来这位上没有1,异或了之后有1了,肯定是最优的 
 	}
} 

ll get_min()   //求最小的异或值 
{
	if( flag ) return 0;    //如果可以获得0就返回0 
	for (int i = 0; i <= 62; i++)
	{
		if( p[i] ) return p[i];   //从低位枚举,有这个数直接返回,后面的数肯定大 
	}
	return 0;   //说明完全没有元素 
}

int main()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		ll x;
		cin >> x;
		insert(x);    //将每个元素插入线性基中 
	}
	return 0;
} 

求集合异或的第k大
将原有的线性重构一下,通过不同元素的异或使得线性基的每个元素每一位上的1在别的元素中都不曾出现。那么他们任意的异或值都会变大,这样就可以根据k来构造了。

int cnt = 0;

void rebuild()
{
	for (int i = 62; i >= 1; i--)
	{
		if( p[i] )   //线性基这一位有值 
		{
			for (int j = i -1; j >= 0; j--)   //遍历低位 
			{
				if( p[i] & (1ll << j) )   //该位为1 
					p[i] ^= p[j];        //异或消去 
			}
		}
	}
	for (int i = 0; i <= 62; i++)    //将线性基整合后放到一个新的集合中 
	{
		if( p[i] ) b[cnt++] = p[i];
	}
}

ll kth(ll k)
{
	if( flag ) k --;   //能表示0,k-- 
	if( k == 0 ) return 0;
	ll res = 0;
	if( k >= ((ll)1 << cnt) ) return -1;  //最多也就2^(cnt)-1中组合了,k还比这大就不存在了 
	for (int i = 0; i < cnt; i++)   //遍历新的集合   
	{
		if( k & ((ll)1<<i) )    //k的这一位为1就异或上这个集合的当前元素 
		{
			res ^= b[i];
		}
	}
	return res;
} 

求区间线性基
维护前缀和的线性基,p[i][62]表示[1…i]这个区间的线性基,还要让靠右的数字尽可能在线性基的高位出现(由于是区间访问,可能会导致这个数不存在,越靠右越不容易被排除掉)。

#include <iostream>
using namespace std;

typedef long long ll;

const int maxn = 1e6 + 5;

ll p[maxn][40];     //记录前缀和的线性基 
int pos[maxn][40];  //记录每一位最后出现的位置 

void insert(int id,ll x,int z)
{
	for (int i = 31; i >= 0; i--)
	{
		if( x & (1ll<<i) )
		{
			if( !p[id][i] )   //为0直接插入 
			{
				p[id][i] = x;
				pos[id][i] = z;
				return;
			}
			if( pos[id][i] < z )  //如果这一位的pos比较小,那么就让新的值替换它 
			{
				swap(x,p[id][i]);
				swap(z,pos[id][i]);
			}
			x ^= p[id][i];   //异或掉这一位 
		}
	}
}

ll get_max(ll l,ll r)   //获得区间的最大线性基 
{
	ll res = 0;
	for (int i = 31; i >= 0; i--)
	{
		if( pos[r][i] >= l )  //必须保证pos大于l 
		{
			if( (res ^ p[r][i]) > res ) res ^= p[r][i];
		}
	}
	return res;
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int t;
	cin >> t;
	while( t-- )
	{
		int n,m;
		cin >> n >> m;
		for (int i = 1; i <= n; i++)
		{
			ll x;
			cin >> x;
			for (int j = 31; j >= 0; j--)   //复制一遍前面的
			{
				p[i][j] = p[i-1][j];
				pos[i][j] = pos[i-1][j];
			}
			insert(i,x,i);
		}
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值