线性基
学习资料:OI Wiki
论记笔记的重要性:20天学的东西,今天做题差点想不起我学过它 (._."ll)
概念
线性基是向量空间的一组基,通常可以解决有关异或的一些题目。
是由一个集合构造出来的另一个集合,有如下性质:
- 线性基的元素能相互异或得到原集合的元素的所有相互异或得到的值。
- 线性基是满足性质 1 的最小的集合。
- 线性基没有异或和为 0 的子集。
- 线性基中每个元素的异或方案唯一,即,线性基中不同的异或组合异或出的数是不一样的。
- 线性基中每个元素的二进制最高位互不相同。
构造方法
对元集合的每个数 \(x\) 从高位向低位扫,如果第 \(i\) 位是1 ,如果 \(p_i\) 不存在,那么令 \(p_i=x\) 并结束扫描,如果存在,令 \(x=x\,xor\,p_i\) 。
void insert(ll x)
{
for(int i=60;i>=0;i--)
{
if(!(x>>i))continue;
if(!p[i]){
p[i]=x;break;
}
x^=p[i];
}
}
例题
题意:给定n个整数(数字可能重复),求在这些数中选取任意个,使得他们的异或和最大。
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define mkp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
const int mod=998244353;
const int UP=52;
struct LinearBasis{
ll a[UP+1];
LinearBasis(){
std::fill(a,a+UP+1,0);
}
void insert(ll t)
{
for(int j=UP;j>=0;j--)
{
if(!((t>>j)&1))continue;
if(!a[j]){
a[j]=t;return;
}
t^=a[j];
}
}
}lib;
int n;
int main()
{
scanf("%d",&n);
ll x;
for(int i=1;i<=n;i++){
scanf("%lld",&x);
lib.insert(x);
}
ll res=0;
for(int i=UP;i>=0;i--)
if((res^lib.a[i])>res)res^=lib.a[i];
printf("%lld\n",res);
}
题意:给定一张无向连通图,要从 1 走到 n ,每条边可以重复走过多次,求路径经过的边权值 XOR 和的最大值。
解:
已知相同权值相互异或为 0 ,如果对一条路径来回走一遍,则会抵消它们的异或贡献。那么,这张无向图的所有的环路的权值都可以 可选择地作为答案的贡献。
那么答案就是为 dis[n] (dis[n] 为从 1 到 n 的一条路径的异或和) 再异或 所有环路相互异或结果 取最大值。即将环路的异或值放进线性基,之后只需从大到小 让答案异或线性基的值取最优值。
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define mkp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
const int mod=998244353;
int head[maxn],to[maxn<<2],nxt[maxn<<2],pcnt;ll ww[maxn<<2];
inline void add(int u,int v,ll w){
to[++pcnt]=v;nxt[pcnt]=head[u];ww[pcnt]=w;head[u]=pcnt;
}
const int UP=60;
struct LinearBasis{
ll a[UP+1];
LinearBasis(){
std::fill(a,a+UP+1,0);
}
void insert(ll t)
{
for(int j=UP;j>=0;j--)
{
if(!((t>>j)&1))continue;
if(!a[j]){
a[j]=t;return;
}
t^=a[j];
}
}
}lib;
int n,m;
bool vis[maxn];ll dis[maxn];
void dfs(int u,ll d){
dis[u]=d;vis[u]=true;
for(int i=head[u];i;i=nxt[i])
if(!vis[to[i]])dfs(to[i],d^ww[i]);
else lib.insert(d^dis[to[i]]^ww[i]);
}
int main()
{
int u,v;ll w;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%lld",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
dfs(1,0);
ll res=dis[n];
for(int i=UP;i>=0;i--)
if((res^lib.a[i])>res)res^=lib.a[i];
printf("%lld\n",res);
}
3,XOR序列
题意:有 n 个数,对于任意的 x , y ,能否将 x 与这 n 个数中的任意多个数异或任意多次后变为 y 。
解:
将n个数插入线性基后,即求线性基能否异或得到 x^y 。
注意到线性基有一个性质:线性基中每个元素的二进制最高位互不相同。
那么,只需要对 x 从高位到低位判断,如果 x 在二进制第 i 位为 1,则将 x异或 lib.a[i]。
如果在最后 x 的值变为 0,则答案为 YES
,否则答案为 NO
。
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define mkp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
const int mod=998244353;
const int UP=31;
struct LinearBasis{
ll a[UP+1];
LinearBasis(){
std::fill(a,a+UP+1,0);
}
void insert(ll t)
{
for(int j=UP;j>=0;j--)
{
if(!((t>>j)&1))continue;
if(!a[j]){
a[j]=t;return;
}
t^=a[j];
}
}
}lib;
int n;
int main()
{
scanf("%d",&n);
ll x,y;
for(int i=1;i<=n;i++){
scanf("%lld",&x);
lib.insert(x);
}
int Q;
scanf("%d",&Q);
while(Q--)
{
scanf("%lld%lld",&x,&y);
x^=y;
for(int i=UP;i>=0;i--)
if(x&(1<<i)){
//另一种写法 if(!lib.a[i])break;
x^=lib.a[i];
}
if(!x)puts("YES");
else puts("NO");
}
}
题意:有n个数对: <序号,权值>,求最大的权值和,使得选中数对不存在子集使得其序号异或和为 0 。
解:
当有子集异或和为 0 时,必能将子集分成 1 个数 x 和另一个异或和为 x 的子集。
那么,将数对按权值从大到小排序,在点 i ,只要对于当前集合存在与 i 的序号相同的异或和时,就不要将 i 添加进集合,因为集合中的所有元素的权值都大于等于 i 的权值,舍去 i 是最佳的。
即,排序,判断当前序号值是否能被线性基异或得到,若否,则将 当前点权值加进答案,并将当前点序号更新进线性基。
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define mkp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const int inf=0x3f3f3f3f;
const int mod=998244353;
const int UP=62;
struct LinearBasis{
ll a[UP+1];
LinearBasis(){
std::fill(a,a+UP+1,0);
}
void insert(ll t)
{
for(int j=UP;j>=0;j--)
{
if(!((t>>j)&1))continue;
if(!a[j]){
a[j]=t;return;
}
t^=a[j];
}
}
}lib;
struct P{
ll v;int w;
bool operator<(const P&p)const{return w>p.w;}
}p[maxn];
int n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%lld%d",&p[i].v,&p[i].w);
sort(p+1,p+1+n);
int res=0;ll x;
for(int i=1;i<=n;i++)
{
x=p[i].v;
for(int j=UP;j>=0;j--)
if(x&(1ll<<j))
{
if(!lib.a[j])break;
x^=lib.a[j];
}
if(x)res+=p[i].w,lib.insert(x);
}
printf("%d\n",res);
}
标签:const,int,ll,UP,异或,数学,线性 来源: https://www.cnblogs.com/kkkek/p/13862676.html
本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享; 2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关; 3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关; 4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除; 5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。