线性基总结(题目合集)

线性基的三大性质:

线性基能相互异或得到原集合的所有相互异或得到的值。

线性基是满足性质1的最小的集合。

线性基没有异或和为0的子集。

模板:

const int MaxBasis = 62;///二进制位数
struct LB {
    ll base[MaxBasis+10]; bool rel; int sz;
    vector<ll> Basis;/// 线性基(向量)
    LB() { memset(base, 0, sizeof(base)); rel = false; sz = 0; Basis.clear();}
    void init() {
        memset(base,0,sizeof(base));rel = false;sz = 0;Basis.clear();
    }
    int add(ll x) {				///加入线性基中
        for (int i = MaxBasis; i >= 0; --i) {
            if (!(x >> i & 1)) continue;
            if (base[i]) x ^= base[i];
            else {
                for (int j = 0; j < i; ++j) if (x >> j & 1) x ^= base[j];
                for (int j = i+1; j <= MaxBasis; ++j) if (base[j] >> i & 1) base[j] ^= x;
                base[i] = x, ++sz;
                return 1;
            }
        }
        rel = true;
        return 0;
    }
    ll Max(ll ans = 0) {			///取最大
        for(int i=0; i<=MaxBasis;i++) ans = max(ans,ans^base[i]);
        return ans;
    }
    ll Min(ll ans = 0) {			///取最小
        for(int i=0; i<= MaxBasis;i++) ans = min(ans,ans^base[i]);
        return ans;
    }
    void GetBasis() {				///构造向量,用于之后求第k小
        for (int i = 0; i <= MaxBasis; ++i)
            if (base[i]) Basis.push_back(base[i]);
    }
    void bin(struct LB &b) {			/// 线性基求并(合并)
        for(int i=0;i <= MaxBasis;i++) if(b.base[i])
            add(b.base[i]);
    }
    LB jiao(LB a,LB b){ /// 线性基求交
        LB g,tmp = a;
        ll cur,d;
        for(int i = 0;i <= MaxBasis;i++) if(b.base[i]){
            cur = 0,d = b.base[i];
            for(int j = i;j >= 0;j--) if(d>>j&1){
                if(tmp.base[j]){
                    d ^= tmp.base[j],cur ^= a.base[j];
                    if(d) continue;
                    g.base[i] = cur;
                }else tmp.base[j] = d, a.base[j] = cur;
                break;
            }
        }
        return g;
    }
    ll Min_Kth(ll k) {				/// 线性基中第k小
        if(rel) k--;				/// 线性基未满存在0
        if(k >= ((ll)1<<sz)) return -1;
        ll ans = 0;
        for(int i=0;i<sz;i++) if(k & ((ll)1<<i)){
            ans ^= Basis[i];
        }
        return ans;
    }
};

基本应用有求子集异或的最大(小)值、第k大,线性基合并和求交集,模板里面都有。具体讲解可以参考https://blog.csdn.net/a_forever_dream/article/details/83654397

1、BJWC 2011 元素

题意:有n个东西,每个东西能选当且仅当它的元素序号不能通过之前选过的东西的元素序号异或得到,在此前提下,求魔力的最大值。

分析:按魔力从大到小排序,贪心选取魔力值大的,插入线性基,证明见https://www.luogu.org/problemnew/solution/P4570

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6+5;

const int MaxBasis = 62;///二进制位数
struct LB {
    ll base[MaxBasis+10]; bool rel; int sz;
    vector<ll> Basis;/// 线性基(向量)
    LB() { memset(base, 0, sizeof(base)); rel = false; sz = 0; Basis.clear();}
    void init() {
        memset(base,0,sizeof(base));rel = false;sz = 0;Basis.clear();
    }
    int add(ll x) {				///加入线性基中
        for (int i = MaxBasis; i >= 0; --i) {
            if (!(x >> i & 1)) continue;
            if (base[i]) x ^= base[i];
            else {
                for (int j = 0; j < i; ++j) if (x >> j & 1) x ^= base[j];
                for (int j = i+1; j <= MaxBasis; ++j) if (base[j] >> i & 1) base[j] ^= x;
                base[i] = x, ++sz;
                return 1;
            }
        }
        rel = true;
        return 0;
    }
    ll Max(ll ans = 0) {			///取最大
        for(int i=0; i<=MaxBasis;i++) ans = max(ans,ans^base[i]);
        return ans;
    }
    ll Min(ll ans = 0) {			///取最小
        for(int i=0; i<= MaxBasis;i++) ans = min(ans,ans^base[i]);
        return ans;
    }
    void GetBasis() {				///构造向量,用于之后求第k小
        for (int i = 0; i <= MaxBasis; ++i)
            if (base[i]) Basis.push_back(base[i]);
    }
    void bin(struct LB &b) {			/// 线性基求并(合并)
        for(int i=0;i <= MaxBasis;i++) if(b.base[i])
            add(b.base[i]);
    }
    LB jiao(LB a,LB b){ /// 线性基求交
        LB g,tmp = a;
        ll cur,d;
        for(int i = 0;i <= MaxBasis;i++) if(b.base[i]){
            cur = 0,d = b.base[i];
            for(int j = i;j >= 0;j--) if(d>>j&1){
                if(tmp.base[j]){
                    d ^= tmp.base[j],cur ^= a.base[j];
                    if(d) continue;
                    g.base[i] = cur;
                }else tmp.base[j] = d, a.base[j] = cur;
                break;
            }
        }
        return g;
    }
    ll Min_Kth(ll k) {				/// 线性基中第k小
        if(rel) k--;				/// 线性基未满存在0
        if(k >= ((ll)1<<sz)) return -1;
        ll ans = 0;
        for(int i=0;i<sz;i++) if(k & ((ll)1<<i)){
            ans ^= Basis[i];
        }
        return ans;
    }
};

int n;
struct nd {
    ll id,w;
}p[N];
bool cmp(nd x,nd y) {
    return x.w>y.w;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lld%lld",&p[i].id,&p[i].w);
    sort(p+1,p+1+n,cmp);
    LB lb;
    ll ans(0);
    for(int i=1;i<=n;i++)
        if(lb.add(p[i].id)) ans += p[i].w;
    printf("%lld\n",ans);
	return 0;
}

2、SCOI 2016 幸运数字

题意:求树上路径点权集合子集异或的最大值。

分析:线性基合并模板题,倍增的思想,和fa数组一样的道理,还是求出lca分情况讨论的套路。

代码:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e4 + 10;
ll a[N],b[N][20][70],dep[N],ans[70],fa[N][20];
vector<int> g[N];
ll read() {
    ll x = 0, f = 1;
    char ch = getchar();
    while(ch>'9'||ch<'0'){
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9') {
        x = x * 10 + ch-'0';
        ch = getchar();
    }
    return x * f;
}
void add(ll B[],ll x) {
    for (int i = 63; i >= 0; i--) {
        if (x & (1ll<<i)) {
            if (B[i]) x^=B[i];
            else {
                B[i] = x;
                break;
            }
        }
    }
}
void mg(ll B1[],ll B2[]){
    for (int i = 63; i >= 0; i--)
        if (B2[i]) add(B1,B2[i]);
}
void dfs(int f,int u) {
    fa[u][0] = f;
    dep[u] = dep[f] + 1;
    for (int i = 1; i <= 15; i++) {
        fa[u][i] = fa[fa[u][i-1]][i-1];
        mg(b[u][i], b[u][i-1]);
        mg(b[u][i] ,b[fa[u][i-1]][i-1]);
    }
    for (int i = 0; i < g[u].size(); i++) {
        if (f == g[u][i]) continue;
        dfs(u,g[u][i]);
    }
}
void lca(int x,int y) {
    memset(ans,0,sizeof(ans));
    if (dep[x] < dep[y]) swap(x,y);
    for (int i = 15; i >= 0; i--)
        if (dep[fa[x][i]] >= dep[y]) {
            mg(ans,b[x][i]);
            x = fa[x][i];
        }
    if (x == y) {
        mg(ans,b[x][0]);
        return;
    }
    for (int i = 15; i >= 0; i--)
        if (fa[x][i] != fa[y][i]) {
            mg(ans,b[x][i]);
            mg(ans,b[y][i]);
            x = fa[x][i];
            y = fa[y][i];
        }
    mg(ans,b[x][0]);
    mg(ans,b[y][0]);
    mg(ans,b[fa[x][0]][0]);
}
int main() {
    ll n = read(),q = read(),x,y;
    for (ll i = 1; i <= n; i++)
        add(b[i][0],a[i] = read());
    for(ll i = 1;i < n; i++){
        x = read(),y = read();
        g[x].push_back(y);
        g[y].push_back(x);
    }
    dfs(0,1);
    while(q--) {
        x = read(),y = read();
        lca(x,y);
        ll res = 0;
        for (int i = 63; i >= 0; i--)
            res = max(res,res^ans[i]);
        printf("%lld\n", res);
    }
    return 0;
}

3、TJOI 2008 彩灯

题意:m个开关,每个开关控制一些灯泡,求出现的不同局面数。(我们可以看成一个0/1串,初始是一个全部为0的串,要求经过这些开关的操作后,出现的不同的0/1串的个数)

分析:开关就相当于异或操作,这是使用线性基的前提,知道这个的话这题就是个线性基的模板题了,证明见https://www.luogu.org/problemnew/solution/P3857

代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=51,mod=2008;
int cnt;
ll arr[N];
void add (ll box)
{
    for(int i=50; i>=0; i--)
    {
        if(!(box>>i&1)) continue;
        if(!arr[i])
        {
            ++cnt,arr[i]=box;
            break;
        }
        else box^=arr[i];
    }
}
int n,m;
char s[N];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1; i<=m; i++)
    {
        scanf("%s",s);
        int len=strlen(s);
        ll x=0;
        for(int i=0; i<len; i++) x+=(1ll<<(n-i))*(s[i]=='O');
        add(x);
    }
    printf("%lld\n",(1ll<<cnt)%mod);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值