線性基 復習總結


在八十中聽lyd講了一些奇怪的東西

(至今不理解小伙伴們為什么要去聽弦圖和三維凸包

不過線性基還是很有用的

因為比較簡單,所以飛快的把所有題目都刷完了OwO

在東北育才聽鄒雨恆學長講了一道很不錯的題目,然而一直找不到提交地址

至今沒找到,sad story。。

貌似當初還坑了一道HEOI的題目OwO(有時間去補補

 

所謂線性基,就是向量空間下的一組基底(線性無關)

求法是利用高斯消元,不難證明k維向量空間的線性基最多有k個

貌似如果不是在異或空間里,唯一的用處就是判斷是否線性相關了OwO

這個東西和擬陣配合起來可以得到一些貪心的做法

如果在異或空間里,由於二進制的特殊性,問題一般就比較多樣化

 

消法一般有兩種,一種是直接消成上三角陣,一種是消成對角線陣

第一種跑得比較快,但第二種貪心起來比較價簡單,也算各有利弊

這樣是因為譬如1,3兩個數,用第一種消法得到1,3兩個線性基,然而因為3^1=2,所以貪心起來要多判斷一些東西

第二種消法直接得到1,2,從大到小選線性基異或只會越來越大

同時對角陣有一個特性是有線性基的位上只有線性基的那一位為1,其余均為0

但是這里要注意沒有線性基的位不一定為0,譬如只有一個數3

 

hdu 3949 XOR

OwO 因為一直習慣消成上三角,然后被坑了好久

消成對角線直接逐位確定就可以了,特判下0

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;
const int maxn=10010;
int T,n,m,cnt,kase;
LL a[maxn],Num[72],k;

void Get_base(){
    memset(Num,0,sizeof(Num));
    for(int i=1;i<=n;++i){
        for(int j=62;j>=0;--j){
            if(a[i]>>j&1){
                if(Num[j])a[i]^=Num[j];
                else{
                    Num[j]=a[i];
                    for(int k=j-1;k>=0;--k)if(Num[k]&&Num[j]>>k&1)Num[j]^=Num[k];
                    for(int k=j+1;k<=62;++k)if(Num[k]>>j&1)Num[k]^=Num[j];
                    break;
                }
            }
        }
    }return;
}

int main(){
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
        Get_base();scanf("%d",&m);cnt=0;
        for(int i=0;i<=62;++i)if(Num[i])cnt++;
        kase++;
        printf("Case #%d:\n",kase);
        while(m--){
            scanf("%lld",&k);
            if(cnt==n)k++;
            if(k>(1LL<<cnt)){printf("-1\n");continue;}
            LL ans=0;int tmp=cnt;
            for(int i=62;i>=0;--i){
                if(Num[i]){
                    LL now=(1LL<<(tmp-1));
                    if(k>now)k-=now,ans^=Num[i];
                    tmp--;
                }
            }printf("%lld\n",ans);
        }
    }return 0;
}

BZOJ 2115 XOR

任取一條S->T的路徑d,然后利用DFS樹求出所有的簡單環

問題轉化成求這些簡單環的異或和的一個子集T,使得T的異或和與d的異或和最大

然后搞出線性基貪心就可以了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;
 
typedef long long LL;
const int maxn=50010;
int n,m,u,v;
int h[maxn],cnt=0,tot=0;
bool vis[maxn];
LL w;
LL dis[maxn];
LL A[300010];
struct edge{
    int to,next;
    LL w;
}G[300010];
 
void add(int x,int y,LL z){++cnt;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt;}
void DFS(int u){
    vis[u]=true;
    for(int i=h[u];i;i=G[i].next){
        int v=G[i].to;
        if(!vis[v]){
            dis[v]=dis[u]^G[i].w;
            DFS(v);
        }else A[++tot]=dis[u]^G[i].w^dis[v];
    }return;
}
void read(int &num){
    num=0;char ch=getchar();
    while(ch<'!')ch=getchar();
    while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar();
}
void Gauss(){
    int to,now=1;
    for(LL i=1LL<<62;i;i>>=1){
        for(to=now;to<=tot;++to){
            if(A[to]&i)break;
        }
        if(to==tot+1)continue;
        swap(A[to],A[now]);
        for(int j=1;j<=tot;++j){
            if(j==now)continue;
            if(A[j]&i)A[j]^=A[now];
        }now++;
    }return;
}
 
int main(){
    read(n);read(m);
    for(int i=1;i<=m;++i){
        read(u);read(v);
        scanf("%lld",&w);
        add(u,v,w);add(v,u,w);
    }
    DFS(1);
    Gauss();
    LL ans=dis[n];
    for(int i=1;A[i];++i){
        if((ans^A[i])>ans)ans^=A[i];
    }printf("%lld\n",ans);
    return 0;
}

CQOI 新Nim游戲 

JLOI 裝備購買

OwO 都是直接利用擬陣貪心,排序之后塞進線性基里就可以了

 

BZOJ 2844 albus就是要第一個出場

跟hdu那道題互為對偶問題,如果懶的話可以直接二分搞QAQ

考慮有n個數,消完后得到k個線性基,則xor起來會有2^k種不同的答案

不難發現每種答案的方案數為2^(n-k)

這樣我們可以消成對角陣之后逐位確定就可以了

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
 
const int maxn=100010;
const int mod=10086;
int n,k,cnt,ans,tmp;
int a[maxn],p[maxn];
int Num[42];
 
void read(int &num){
    num=0;char ch=getchar();
    while(ch<'!')ch=getchar();
    while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar();
}
void Get_base(){
    for(int i=1;i<=n;++i){
        for(int j=30;j>=0;--j){
            if(a[i]>>j&1){
                if(Num[j])a[i]^=Num[j];
                else{
                    Num[j]=a[i];
                    for(int k=j-1;k>=0;--k)if(Num[k]&&Num[j]>>k&1)Num[j]^=Num[k];
                    for(int k=j+1;k<=30;++k)if(Num[k]>>j&1)Num[k]^=Num[j];
                    break;
                }
            }
        }
    }return;
}
 
int main(){
    read(n);p[0]=1;
    for(int i=1;i<=n;++i)p[i]=(p[i-1]<<1)%mod;
    for(int i=1;i<=n;++i)read(a[i]);
    read(k);Get_base();
    for(int i=0;i<=30;++i)if(Num[i])cnt++;
    tmp=cnt;
    for(int i=30;i>=0;--i){
        if(Num[i]){
            if(k>>i&1){
                ans=ans+(1LL<<(tmp-1))*p[n-cnt]%mod;
                ans%=mod;
            }tmp--;
        }
    }ans++;ans%=mod;
    printf("%d\n",ans);
    return 0;
}

BZOJ 2728 與非

不難發現這個操作可以表示二進制運算的全集

然后由於每個數可以取任意多次(lyd問我的時候我沒看到這句話,就口胡說可能有一些性質把,太羞恥了嗚嗚

線性基就是那些運算時的等價類(即某些位無論怎么樣運算都是一樣的

在一個等價類的充要條件是這些位在任意數中都是一樣的

之后搞出這些線性基逐位確定即可

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
 
const int maxn=1010;
typedef long long LL;
int n,k,cnt;
int fa[maxn];
LL a[maxn],w[maxn];
LL L,R;
 
bool cmp(const int &a,const int &b){return a>b;}
bool check(int i,int j){
    for(int k=1;k<=n;++k){
        int A=(a[k]>>i&1);
        int B=(a[k]>>j&1);
        if(A^B)return false;
    }return true;
}
LL DFS(LL now,int len,int la){
    if(now>=(1LL<<(len+1)))return 1LL<<la;
    if(now<0)return 0;
    if(len==-1)return 1;
    if(!w[len])return DFS(now,len-1,la);
    else{
        if(now>>len&1)return DFS(now-w[len],len-1,la-1)+(1LL<<(la-1));
        else return DFS(now,len-1,la-1);
    }
}
 
int main(){
    scanf("%d%d",&n,&k);
    scanf("%lld%lld",&L,&R);
    for(int i=1;i<=n;++i)scanf("%lld",&a[i]);
    for(int i=0;i<k;++i)fa[i]=i;
    for(int i=k-1;i>=0;--i){
        if(fa[i]==i){
            for(int j=i-1;j>=0;--j)if(check(i,j))fa[j]=i;
        }
    }
    for(int i=k-1;i>=0;--i){
        if(fa[i]==i){
            w[i]|=(1LL<<i);cnt++;
            for(int j=i-1;j>=0;--j)if(fa[j]==i)w[i]|=(1LL<<j);
        }
    }
    printf("%lld\n",DFS(R,k-1,cnt)-DFS(L-1,k-1,cnt));
    return 0;
}

BZOJ 4568

因為線性基可以合並,所以顯然用個奇怪的數據結構維護一下就可以了

做法有很多種,目測樹鏈剖分+線段樹會T,log太多啦

第一種做法是倍增,處理i向上2^j的點們的線性基,然后倍增合並就可以了

第二種做法是樹分治,我們對於每一層重心處理每個點到這層重心的鏈的線性基

每次查詢只需要在分治樹上搞到LCA然后之后把兩條鏈的線性基合並就可以了

(LCA是因為分治到這步的時候u和v兩個點分開了,所以兩條鏈不相交而且合起來就是u->v的鏈

為了省事每層重心用map記錄的位置,莫名多log,然后就跑得好慢嗚嗚

#include<stdio.h>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
using namespace std;
 
const int maxn=20010;
const int oo=0x7fffffff;
typedef long long LL;
int n,m,u,v,g,sum,now;
int h[maxn],cnt=0;
int fa[maxn],dep[maxn],f[maxn],w[maxn];
bool vis[maxn];
LL c[maxn];
LL Ans[61],ans;
struct edge{
    int to,next;
}G[maxn<<1];
struct base{
    int cnt;
    vector<LL>V[61];
    map<int,int>p;
    void init(int u,LL v){
        for(int i=0;i<=60;++i)V[i].push_back(0);
        p[u]=0;
        for(int i=60;i>=0;--i){
            if(v>>i&1){V[i][cnt]=v;break;}
        }return;
    }
    void Get_new(int u,int f,LL v){
        int now=p[f];
        for(int i=0;i<=60;++i)V[i].push_back(V[i][now]);
        ++cnt;p[u]=cnt;
        for(int i=60;i>=0;--i){
            if(v>>i&1){
                if(V[i][cnt])v^=V[i][cnt];
                else {V[i][cnt]=v;break;}
            }
        }return;
    }
}b[maxn];
 
void add(int x,int y){
    ++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt;
}
void cmax(int &a,int b){if(b>a)a=b;}
void Get_G(int u,int fa){
    w[u]=1;f[u]=0;
    for(int i=h[u];i;i=G[i].next){
        int v=G[i].to;
        if(vis[v]||v==fa)continue;
        Get_G(v,u);w[u]+=w[v];
        cmax(f[u],w[v]);
    }cmax(f[u],sum-w[u]);
    if(f[u]<f[g])g=u;
}
void Get_base(int u,int f){
    b[now].Get_new(u,f,c[u]);
    for(int i=h[u];i;i=G[i].next){
        int v=G[i].to;
        if(vis[v]||v==f)continue;
        Get_base(v,u);
    }return;
}
void Get_sz(int u,int f){
    w[u]=1;
    for(int i=h[u];i;i=G[i].next){
        int v=G[i].to;
        if(vis[v]||v==f)continue;
        Get_sz(v,u);w[u]+=w[v];
    }return;
}
void Get_div(int u,int f){
    fa[u]=f;vis[u]=true;
    now=u;b[now].init(u,c[u]);
    for(int i=h[u];i;i=G[i].next){
        int v=G[i].to;
        if(vis[v])continue;
        Get_base(v,u);
    }Get_sz(u,-1);
    for(int i=h[u];i;i=G[i].next){
        int v=G[i].to;
        if(vis[v])continue;
        g=0;sum=w[v];Get_G(v,-1);
        dep[g]=dep[u]+1;
        Get_div(g,u);
    }return;
}
int LCA(int u,int v){
    while(u!=v){
        if(dep[u]<dep[v])swap(u,v);
        u=fa[u];
    }return u;
}
 
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)scanf("%lld",&c[i]);
    for(int i=1;i<n;++i){
        scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    g=0;sum=n;f[0]=oo;
    Get_G(1,-1);Get_div(g,-1);
    while(m--){
        scanf("%d%d",&u,&v);
        int lca=LCA(u,v);
        int pos=b[lca].p[u];
        for(int i=0;i<=60;++i)Ans[i]=b[lca].V[i][pos];
        pos=b[lca].p[v];
        for(int i=0;i<=60;++i){
            LL v=b[lca].V[i][pos];
            for(int j=60;j>=0;--j){
                if(v>>j&1){
                    if(Ans[j])v^=Ans[j];
                    else{Ans[j]=v;break;}
                }
            }
        }ans=0;
        for(int i=60;i>=0;--i)ans=max(ans,ans^Ans[i]);
        printf("%lld\n",ans);
    }
    return 0;
}

BZOJ 3569

OwO 鬼畜題目

做法是利用隨機化,我們弄出一棵DFS樹

不難發現不連通當且僅當存在一條樹邊,滿足這條樹邊和所有覆蓋到這條樹邊的非樹邊都被刪除了

給每個非樹邊隨機一個權值,然后樹邊的權值是覆蓋他的非樹邊的權值和

那么判斷是否聯通就只需要判斷給定邊是否存在一個子集異或和為0

用線性基搞一搞就可以了,因為隨機的權值所以沖突概率很小

如果不強制在線直接一發分治+並查集就可以了,每次回溯的時候撤銷並查集操作

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
 
const int maxn=500010;
const int mod=(1<<30)-1;
int n,m,q,tot,la,k,x;
int fa[maxn];
int vis[maxn],dep[maxn];
bool check[maxn];
int h[maxn],cnt=0;
int Ans[42];
struct edge{
    int to,next;
}G[maxn<<1];
struct Edge{
    int u,v,w;
}c[maxn];
 
void add(int x,int y){
    ++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt;
}
int ufs(int x){return fa[x]==x?x:fa[x]=ufs(fa[x]);}
void read(int &num){
    num=0;char ch=getchar();
    while(ch<'!')ch=getchar();
    while(ch>='0'&&ch<='9')num=num*10+ch-'0',ch=getchar();
}
void DFS(int u,int f){
    for(int i=h[u];i;i=G[i].next){
        int v=G[i].to;
        if(v==f)continue;
        dep[v]=dep[u]+1;
        DFS(v,u);vis[u]^=vis[v];
    }return;
}
 
int main(){
    read(n);read(m);tot=n;
    for(int i=1;i<=n;++i)fa[i]=i;
    for(int i=1;i<=m;++i){
        read(c[i].u);read(c[i].v);c[i].w=(rand()&mod);
        int d1=ufs(c[i].u),d2=ufs(c[i].v);
        if(d1!=d2){
            fa[d1]=d2;tot--;check[i]=true;
            add(c[i].u,c[i].v);add(c[i].v,c[i].u);
        }else vis[c[i].u]^=c[i].w,vis[c[i].v]^=c[i].w;
    }read(q);
    if(tot!=1){
        while(q--)printf("Disconnected\n");
        return 0;
    }DFS(1,-1);
    for(int i=1;i<=m;++i){
        if(check[i]){
            if(dep[c[i].u]<dep[c[i].v])swap(c[i].u,c[i].v);
            c[i].w=vis[c[i].u];
        }
    }
    while(q--){
        memset(Ans,0,sizeof(Ans));
        read(k);int cnt=k;
        while(k--){
            read(x);x^=la;
            int v=c[x].w;
            for(int i=29;i>=0;--i){
                if(v>>i&1){
                    if(Ans[i])v^=Ans[i];
                    else{Ans[i]=v;break;}
                }
            }
        }
        for(int i=0;i<=29;++i)if(Ans[i])cnt--;
        if(!cnt)printf("Connected\n"),la++;
        else printf("Disconnected\n");
    }return 0;
}

codeforces 662 A

考慮我們求出a序列的異或和S

之后令ci=ai^bi

原問題轉化為是否存在c的一個子集和S的異或和為0

我們求c的線性基判斷就可以了

至於有多少個嘛,前面albus那道題目已經提過了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;

const int maxn=500010;
typedef long long LL;
int n;
LL Num[72],ans;
LL a[maxn],b[maxn],S=0;

void Get_base(){
	for(int i=1;i<=n;++i){
		for(int j=62;j>=0;--j){
			if(a[i]>>j&1){
				if(!Num[j]){Num[j]=a[i];break;}
				else a[i]^=Num[j];
			}
		}
	}return;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%I64d%I64d",&a[i],&b[i]);
	for(int i=1;i<=n;++i)S^=a[i],a[i]^=b[i];
	Get_base();
	bool flag=false;int cnt=0;;
	for(int i=62;i>=0;--i){
		if(S>>i&1)S^=Num[i];
		if(Num[i])cnt++;
	}
	if(S){printf("1/1");return 0;}
	ans=1;
	for(int i=1;i<=cnt;++i)ans<<=1;
	printf("%I64d/%I64d\n",ans-1,ans);
	return 0;
	
}

 

OwO 感覺還坑了好多題目

主要就是求子集異或和問題吧,可能會有擬陣,動態規划什么的算法和他結合之類的QAQ

利用二進制的性質思考一下就好啦


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM