线性基大发现

一.构造方法

1.贪心法(每一个数往里插入即可)

/*贪心法构造线性基的特点:
1.从小到大排列
2.各个基的高位可能存在重复的1
2.线性基不是唯一的,与原集合的元素顺序有关*/
void insert(int x){//贪心法
    for(int i=63;i>=0;i--){
        if(!(x>>i)) continue;//x第i位为1
        if(!p[i]){//已存在p[i]
            p[i]=x;
            break;
        }
        x^=p[i];//不存在的话加上再异或
    }
}

2.高斯消元法(统一插入)

/*高斯消元构造线性基特点:
1.从大到小排列
2.各个基的高位没有重复的1*/
void gauss(){
    k=0;
    for(int i=62;i>=0;i--){
        //把当前第i位是1的数换上去
        for(int j=k;j<n;j++){
            if(a[j]>>i&1){
                swap(a[j],a[k]);
                break;
            }
        }
        //当前第i位的所有向量都是0
        if((a[k]>>i&1)==0) continue;
        //当前第i位全部消为0
        for(int j=0;j<n;j++){
            if(j!=k&&(a[j]>>i&1)) a[j]^=a[k];
        }
        //基的个数+1
        k++;
        if(k==n) break;
    }
}

 二.例题

 1.P3812 【模板】线性基

 贪心:

#include<bits/stdc++.h>
using namespace std;
#define fast ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define For(i,j,k) for (int i=(j);i<=(k);i++)
#define rof(i,j,k) for (int i=(j);i>=(k);i--)
#define ull unsigned long long
#define lowbit(x) ((x)&(-(x)))
#define PII pair<int,int>
#define int long long
#define endl "\n"
#define ls rt<<1
#define rs rt<<1|1
typedef long long ll;
const int mod = 998244353;
const int N=2e5+7;
int n,m;
int p[70];
/*贪心法构造线性基的特点:
1.从小到大排列
2.各个基的高位可能存在重复的1
2.线性基不是唯一的,与原集合的元素顺序有关*/
void insert(int x){//贪心法
    for(int i=63;i>=0;i--){
        if(!(x>>i)) continue;//x第i位为1
        if(!p[i]){//已存在p[i]
            p[i]=x;
            break;
        }
        x^=p[i];//不存在的话加上再异或
    }
}
void solve(){
    cin>>n;
    int a;
    for(int i=1;i<=n;i++){
        cin>>a;
        insert(a);
    }
    int ans=0;
    for(int i=63;i>=0;i--){
        ans=max(ans,ans^p[i]);
    }
    cout<<ans<<endl;
}
signed main(){
    fast;
    int t=1;
    //cin>>t;
    while(t--){
        solve();
    }
}

高斯消元法:

 

#include<bits/stdc++.h>
using namespace std;
#define fast ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define For(i,j,k) for (int i=(j);i<=(k);i++)
#define rof(i,j,k) for (int i=(j);i>=(k);i--)
#define ull unsigned long long
#define lowbit(x) ((x)&(-(x)))
#define PII pair<int,int>
#define int long long
#define endl "\n"
#define ls rt<<1
#define rs rt<<1|1
typedef long long ll;
const int mod = 998244353;
const int N=2e5+7;
int n,m,k;
int a[N];
/*高斯消元构造线性基特点:
1.从大到小排列
2.各个基的高位没有重复的1*/
void gauss(){
    k=0;
    for(int i=62;i>=0;i--){
        //把当前第i位是1的数换上去
        for(int j=k;j<n;j++){
            if(a[j]>>i&1){
                swap(a[j],a[k]);
                break;
            }
        }
        //当前第i位的所有向量都是0
        if((a[k]>>i&1)==0) continue;
        //当前第i位全部消为0
        for(int j=0;j<n;j++){
            if(j!=k&&(a[j]>>i&1)) a[j]^=a[k];
        }
        //基的个数+1
        k++;
        if(k==n) break;
    }
}
void solve(){
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
    gauss();
    int ans=0;
    for(int i=0;i<k;i++){
        ans^=a[i];
    }
    cout<<ans<<endl;
}
signed main(){
    fast;
    int t=1;
    //cin>>t;
    while(t--){
        solve();
    }
}

排列好从大到小异或即可。  

2. hdu3949

这里记得分三种情况,当k即线性基的个数小于n时会有0的出现,另外当大于2的k次方时说明不存在特判一下即可。 

#include<bits/stdc++.h>
using namespace std;
#define fast ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define For(i,j,k) for (int i=(j);i<=(k);i++)
#define rof(i,j,k) for (int i=(j);i>=(k);i--)
#define ull unsigned long long
#define lowbit(x) ((x)&(-(x)))
#define PII pair<int,int>
#define int long long
#define endl "\n"
#define ls rt<<1
#define rs rt<<1|1
typedef long long ll;
const int mod = 998244353;
const int N=2e5+7;
int n,m,k;
int a[N];
void gauss(){
    k=0;//从第一位开始
    for(int i=62;i>=0;i--){
        //把当前第i位是1的数换上去
        for(int j=k;j<n;j++){
            if(a[j]>>i&1){
                swap(a[j],a[k]);
                break;
            }
        }
        //当前第i位的所有向量都是0
        if((a[k]>>i&1)==0) continue;
        //当前第i位全部消为0
        for(int j=0;j<n;j++){
            if(j!=k&&(a[j]>>i&1)) a[j]^=a[k];
        }
        //基的个数+1
        k++;
        if(k==n) break;
    }
}
void solve(){
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>a[i];
    }
    gauss();
    cin>>m;
    while(m--){
        int num=0;
        cin>>num;
        if(k!=n) num--;//因为线性基中不包括0所以如果小于n说明会有0出现第1个最小的也会有所以--
        if((1LL<<k)<=num) cout<<"-1"<<endl;
        else{
            int ans=0;
            for(int i=0;i<k;i++){
                //第i小实际上就是直接看这个数每一位是不是1就好
                if(num>>i&1) ans^=a[k-i-1];
            }
            cout<<ans<<endl;
        }
    }
}
signed main(){
    fast;
    int t=1;
    cin>>t;
    int T=t;
    while(T--){
        cout<<"Case #"<<t-T<<":"<<endl;
        solve();
    }
}

3.P3857 [TJOI2008] 彩灯 

 

 

 这个把每个数当成进制处理即可。

#include<bits/stdc++.h>
using namespace std;
#define fast ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define For(i,j,k) for (int i=(j);i<=(k);i++)
#define rof(i,j,k) for (int i=(j);i>=(k);i--)
#define ull unsigned long long
#define lowbit(x) ((x)&(-(x)))
#define PII pair<int,int>
#define int long long
#define endl "\n"
#define ls rt<<1
#define rs rt<<1|1
typedef long long ll;
const int mod = 998244353;
const int N=2e6+7;
int n,m,k;
int a[N];
void gauss(){
    k=0;
    for(int i=62;i>=0;i--){
        //把当前第i位是1的数换上去
        for(int j=k;j<m;j++){
            if(a[j]>>i&1){
                swap(a[j],a[k]);
                break;
            }
        }
        //当前第i位的所有向量都是0
        if((a[k]>>i&1)==0) continue;
        //当前第i位全部消为0
        for(int j=0;j<m;j++){
            if(j!=k&&(a[j]>>i&1)) a[j]^=a[k];
        }
        //基的个数+1
        k++;
        if(k==m) break;
    }
}
void solve(){
    cin>>n>>m;
    for(int i=0;i<m;i++){
        string s;
        cin>>s;
        int x=1;
        for(int j=0;j<n;j++){
            if(s[j]=='O'){
                a[i]+=x;
            }
            x*=2;
        }
    }
    gauss();
    int ans=pow(2,k);
    ans=ans%2008;
    cout<<ans<<endl;
}
signed main(){
    fast;
    int t=1;
    //cin>>t;
    while(t--){
        solve();
    }
}

4.P4570 [BJWC2011] 元素

 

 这个就与上面不同了,不可以直接使用高斯消元法因为我们发现这个只取其中一些个,并且与先异或无关,所以这时候我们采用贪心法线性基。

#include<bits/stdc++.h>
using namespace std;
#define fast ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define For(i,j,k) for (int i=(j);i<=(k);i++)
#define rof(i,j,k) for (int i=(j);i>=(k);i--)
#define ull unsigned long long
#define lowbit(x) ((x)&(-(x)))
#define PII pair<int,int>
#define int long long
#define endl "\n"
#define ls rt<<1
#define rs rt<<1|1
typedef long long ll;
const int mod = 998244353;
const int N=2e6+7;
int n,m;
struct node{
    int num;
    int val;
}p[70],a[N];
bool cmp(node x,node y){
    return x.val>y.val;
}
//贪心法最好的就是你可以想办法将你大的先拿再进行线性基解决
void insert(node x){
    for(int i=60;i>=0;i--){
        if((x.num>>i&1)==0) continue;
        if(p[i].num){
            x.num^=p[i].num;
        }else{
            p[i]=x;
            break;
        }
    }
}
void solve(){
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>a[i].num>>a[i].val;
    }
    sort(a,a+n,cmp);
    for(int i=0;i<n;i++){
        insert(a[i]);
    }
    int ans=0;
    for(int i=0;i<60;i++){
        ans+=p[i].val;
    }
    cout<<ans<<endl;
}
signed main(){
    fast;
    int t=1;
    //cin>>t;
    while(t--){
        solve();
    }
}

5.P4301 [CQOI2013] 新Nim游戏 

这就属于很典型的用了个nim游戏的结论。我们只需要让后手无论怎么取都不能将后面异或值为0即可,所以让后面形成线性基,即我们取线性基以外的数即可。

#include<bits/stdc++.h>
using namespace std;
#define fast ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define For(i,j,k) for (int i=(j);i<=(k);i++)
#define rof(i,j,k) for (int i=(j);i>=(k);i--)
#define ull unsigned long long
#define lowbit(x) ((x)&(-(x)))
#define PII pair<int,int>
#define int long long
#define endl "\n"
#define ls rt<<1
#define rs rt<<1|1
typedef long long ll;
const int mod = 998244353;
const int N=2e6+7;
int n,m,k;
int a[N];
int p[N];
int ans=0;
/*我们先手取尽可能少的数,使得局面形成线性基,
这样无论后手怎么取,一定是我们赢*/
void insert(int x){//贪心法
    int y=x;
    for(int i=63;i>=0;i--){
        if(!(x>>i)) continue;//x第i位为1才继续走
        if(!p[i]){//已存在p[i]
            p[i]=x;
            break;
        }
        x^=p[i];//不存在的话加上再异或
    }
    if(x==0) ans+=y;//不是线性基的直接拿走
}
bool cmp(int x,int y){
    return x>y;
}
void solve(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    sort(a+1,a+n+1,cmp);//因为要取最小所以我们排个序
    for(int i=1;i<=n;i++){
        insert(a[i]);
    }
    cout<<ans<<endl;
}
signed main(){
    fast;
    int t=1;
    //cin>>t;
    while(t--){
        solve();
    }
}

 6.P4151 [WC2011] 最大XOR和路径

 

 

简单情况下,图分为一条主链和若干个环,沿着主链走,中间碰到环可以走也可以不走。

相当于把所有的环插入一个集合再考虑要不要异或,求最大值即可。

一般情况下,多条主链和多条环,两条主链就构成一个环,

所以还是相当于把环放入之后用一个总的异或长度在异或换的线性基

#include<bits/stdc++.h>
using namespace std;
#define fast ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define For(i,j,k) for (int i=(j);i<=(k);i++)
#define rof(i,j,k) for (int i=(j);i>=(k);i--)
#define ull unsigned long long
#define lowbit(x) ((x)&(-(x)))
#define PII pair<int,int>
#define int long long
#define endl "\n"
#define ls rt<<1
#define rs rt<<1|1
typedef long long ll;
const int mod = 998244353;
const int N=2e6+7;
int n,m;
int p[70];
int sum[N];
int ans,cnt;
int vis[N];
/*简单情况下,图分为一条主链和若干个环,沿着主链走,中间碰到环可以走也可以不走。
相当于把所有的环插入一个集合再考虑要不要异或,求最大值即可。

一般情况下,多条主链和多条环,两条主链就构成一个环,
所以还是相当于把环放入之后用一个总的异或长度在异或换的线性基*/
struct Edge
{
    int to, w, next;//终点,边权,同起点的上一条边的编号
}edge[N];//边集
int head[N];//head[i],表示以i为起点的第一条边在边集数组的位置(编号)
void init()//初始化
{
    for (int i = 0; i <= n; i++) head[i] = -1;
    cnt = 0;
}
void add_edge(int u, int v, int w)//加边,u起点,v终点,w边权
{
    edge[cnt].to = v; //终点
    edge[cnt].w = w; //权值
    edge[cnt].next = head[u];//以u为起点上一条边的编号,也就是与这个边起点相同的上一条边的编号
    head[u] = cnt++;//更新以u为起点上一条边的编号
}
void insert(int x){//贪心法
    for(int i=63;i>=0;i--){
        if(!(x>>i)) continue;//x第i位为1
        if(!p[i]){//已存在p[i]
            p[i]=x;
            break;
        }
        x^=p[i];//不存在的话加上再异或
    }
}
//可以想一想为什么dfs在这里不会T
void dfs(int x,int s){
    vis[x]=1;
    sum[x]=s;
    for(int i=head[x];i!=-1;i=edge[i].next){
       int y=edge[i].to;
       if(!vis[y]){
        dfs(y,edge[i].w^sum[x]);
       }else{
        insert(edge[i].w^sum[x]^sum[y]);
       }
    }
}
void solve(){
    cin>>n>>m;
    init();
    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        add_edge(u,v,w);
        add_edge(v,u,w);
    }
    dfs(1,0);
    ans=ans^sum[n];//1到n链异或和
    for(int i=63;i>=0;i--){
        ans=max(ans,ans^p[i]);//因为要取大的所以从大的开始异或
    }
    cout<<ans<<endl;
}
signed main(){
    fast;
    int t=1;
    //cin>>t;
    while(t--){
        solve();
    }
}

 7.P3265 [JLOI2015] 装备购买

 

 这个就是一个很普遍性的高斯消元了,我们就是相当于把一个矩阵做成线性基。这道题要注意的一个点事eps开1e-3可以但1e-6等就不能了,可能还是评测数据的原因QAQ。

#include<bits/stdc++.h>
using namespace std;
#define fast ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define For(i,j,k) for (int i=(j);i<=(k);i++)
#define rof(i,j,k) for (int i=(j);i>=(k);i--)
#define ull unsigned long long
#define lowbit(x) ((x)&(-(x)))
#define PII pair<int,int>
#define int long long
#define endl "\n"
#define ls rt<<1
#define rs rt<<1|1
typedef long long ll;
const int mod = 998244353;
const int N=700;
const double eps=1e-3;//这里过小会wa
int n,m,k;
int ans,cnt;
struct node{
    double z[N];
    int c;
}a[N];
int p[N];
bool cmp(node x,node y){
    return x.c<y.c;
}
void gauss(){//高斯消元
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(fabs(a[i].z[j])>eps){//如果(i,j)>0
                if(!p[j]){ //如果p[j]不存在
                    ans+=a[i].c; //累计花费
                    p[j]=i; //记录最高位为第j为的线性基
                    cnt++; //累计个数
                    break;
                }else{ //如果p[j]存在,则进行消元
                    double d=a[i].z[j]/a[p[j]].z[j];
                    for(int k=j;k<=m;k++){
                        a[i].z[k]-=a[p[j]].z[k]*d;
                    }
                }
            }
        }
    }
}
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i].z[j];
        }
    }
    for(int i=1;i<=n;i++){
        cin>>a[i].c;
    }
    sort(a+1,a+n+1,cmp);
    gauss();
    cout<<cnt<<" "<<ans<<endl;
}
signed main(){
    fast;
    int t=1;
    //cin>>t;
    while(t--){
        solve();
    }
}

我觉得最难的还是你知道什么时候用贪心什么时候用高斯消元做线性基,知道以后还要会结合才能做出这类题,所以加油吧!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值