[浅谈] 线性基
\(\color{purple}\text{P3812 【模板】线性基}\)
求最大值。
Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,d[110];
signed main(){
n=read();
for(int i=1;i<=n;i++){
int x=read();
for(int j=49;j>=0;j--)
if(x&(1ll<<j))
if(d[j])x^=d[j];
else{d[j]=x;break;}
}
int ans=0;
for(int i=49;i>=0;i--){
if(!(ans&(1ll<<i))){
ans^=d[i];
}
}
printf("%lld\n",ans);
return 0;
}
\(\color{purple}\text{P3857 [TJOI2008]彩灯}\)
求能表示的数的个数。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,m,d[55],ans;char t[55];
signed main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
scanf("%s",t+1);int x=0;
for(int j=1;j<=n;j++)x=(x<<1)+(t[j]=='O');
for(int j=49;j>=0;j--)
if(x&(1ll<<j)){
if(d[j])x^=d[j];
else{
d[j]=x,ans++;break;
}
}
}
printf("%lld\n",(1ll<<ans)%2008);
return 0;
}
\(\color{purple}\text{P4570 [BJWC2011]元素}\)
题意
每个宝石有一个 \(a,b\) ,求出 \(\max(\sum b)\) 满足所选宝石中没有任意宝石组合可以 \(\text{a}\) 异或和为 \(0\) 。
解法
如果一个数与线性基中的数的组合异或和为 \(0\) ,那么他对线性及来说没有用,因此把他插入线性基时无法成功。
我们贪心按照 \(b\) 从大到小选择宝石,可以插入线性基就选。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1010;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,ans,d[65];
pair<int,int>a[N];
bool cmp(pair<int,int> a,pair<int,int> b){
return a.second>b.second;
}
signed main(){
n=read();
for(int i=1;i<=n;i++){
a[i].first=read();
a[i].second=read();
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++){
for(int j=64;j>=0;j--){
if(a[i].first&(1ll<<j)){
if(d[j])a[i].first^=d[j];
else {
d[j]=a[i].first;
ans+=a[i].second;
break;
}
}
}
}
printf("%lld\n",ans);
return 0;
}
\(\color{purple}\text{P4301 [CQOI2013] 新Nim游戏}\)
众所周知 \(Nim\) 的先手必胜条件是所有石子异或和不为 \(0\),因为后手能移走一些石子,所以我们的目的是先手移走后剩下的石子怎么改变都无法凑出异或和为零的局面。
我们从大到小枚举每堆石子是否保留,如果无法插入线性基,说明他会和已选石子异或和为零,我们就把它计入答案。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1010;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,ans,d[65],a[N];
bool cmp(int a,int b){return a>b;}
signed main(){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++){
int tmp=a[i];
for(int j=32;j>=0;j--){
if(a[i]&(1ll<<j)){
if(d[j])a[i]^=d[j];
else {
d[j]=a[i];
break;
}
}
}
if(!a[i])ans+=tmp;
}
printf("%lld\n",ans);
return 0;
}
\(\color{purple}\text{k小异或和}\)
首先我们把数插入线性基,对于能求第 \(k\) 小的线性基,我们会想要让每个 \(d[i]\) 尽量小。所以需要
然后把线性基看成一种新的进制,在 第 \(i\) 位为 \(1\) 就异或上 \(d[i]\) 。
最后特殊处理一下 \(0\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=65;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int T,n,d[N],st[N],top;
void solve(int xx){
printf("Case #%lld:\n",xx);
n=read();memset(d,0,sizeof(d));
int hv_0=0;
for(int i=1;i<=n;i++){
int x=read();
for(int j=63;j>=0;j--){
if(x&(1ll<<j)){
if(d[j])x^=d[j];
else{
d[j]=x;
break;
}
}
if(!x){
hv_0=1;
}
}
}
for(int j=63;j>=0;j--){
for(int l=j-1;l>=0;l--){
if(d[j]&(1ll<<l))
d[j]^=d[l];
}
}
top=0;
for(int j=0;j<=63;j++)if(d[j])st[++top]=d[j];
int q=read();
for(int i=1;i<=q;i++){
int k=read();
if(hv_0){
k--;
}
if(k>(1ll<<top)-1)printf("-1\n");
else{
int sum=0;
for(int j=top;j>=1;j--)
if(k&(1ll<<j-1))
sum^=st[j];
printf("%lld\n",sum);
}
}
return;
}
signed main(){
// freopen("1.out","w",stdout);
T=read();
for(int i=1;i<=T;i++)
solve(i);
return 0;
}
\(\color{purple}\text{P4151 [WC2011]最大XOR和路径}\)
显然,我们从任意一个点经过链走到一个环上,再回到原点,那么除了环上的点,链上的点走了两遍,异或和为零。所以我们可以再不改变位置的情况下,给答案异或上一个环上的点的异或和。
我们找到图上的所有环,把权值加入线性基,然后再找一条到 \(n\) 的路径(任意一条即可,因为如果有多条路径,说明1和n也在一个大环上,可以通过异或大环改变路径),然后求线性基异或上路径权值的最大值。
关键在于怎么找环,我们给每个点一个 \(dis[i]\) 存到 \(i\) 的链的权值,然后当从 \(j\) 走到已走的点 \(i\),环上权值即为 \(w \bigoplus dis[i]\bigoplus dis[j]\) 。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int KK=110,N=50010,M=101000;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int n,m,d[KK],dis[N],vis[N];
int head[N],last[M],tot,w[M],to[M];
void Dadd(int u,int v,int t){
to[++tot]=v,w[tot]=t,last[tot]=head[u],head[u]=tot;
to[++tot]=u,w[tot]=t,last[tot]=head[v],head[v]=tot;
return;
}
set<int>s;
void dfs(int u,int sum){
dis[u]=sum;vis[u]=1;
for(int i=head[u];i;i=last[i]){
int v=to[i];
if(!vis[v])dfs(v,sum^w[i]);
else s.insert(dis[v]^sum^w[i]);
}
return;
}
signed main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),t=read();
Dadd(u,v,t);
}
dfs(1,0);
set<int>::iterator it;
for(it=s.begin();it!=s.end();it++){
int x=*it;
for(int j=63;j>=0;j--)
if(x&(1ll<<j)){
if(d[j])x^=d[j];
else{
d[j]=x;
break;
}
}
}
int ans=dis[n];
for(int j=63;j>=0;j--){
if(!(ans&1ll<<j))ans^=d[j];
}
printf("%lld\n",ans);
return 0;
}
\(\color{purple}\text{CF724G Xor-matic Number of the Graph}\)
首先与上题一样,把每个环加入线性基。我们“暴力枚举路径端点然后用线性基求所有可能的权值的话”太慢了。
我们考虑按位求,对于第 \(j\) 位,如果线性基能表示出这一位的话,那么线性基中一定有一半能表示的数有这一位,所以无论左右端点间的路径如何,与线性基异或总会有一半的数在这一位做出贡献。
\(ans+=2^j2^{k-1}\frac{n(n-1)}{2}\)( \(k\) 为线性基中数的数量)
如果不能,那么路径为在这位上为 \(1\) 时全部数都有贡献,反之都没有,路径的值为 \(dis[u]\bigoplus dis[v]\),所以我们只要统计一下 \(dis[u]\) 在 \(j\) 位为 \(1\) ,\(0\) 的个数即可。
\(ans+=2^j2^kx(n-x)\)( \(x\) 为 \(dis[u]\) 在 \(j\) 位为\(1\) 的个数)
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int KK=110,N=1e5+11,M=4e5+12,mod=1e9+7;
int read(){
int x=0,f=1;char c=getchar();
while(c>'9' || c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0' && c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
return x*f;
}
int ans,pos[N],sumpos,n,m,d[KK],dis[N],vis[N];
int head[N],last[M],tot,w[M],to[M],kd;
void Dadd(int u,int v,int t){
to[++tot]=v,w[tot]=t,last[tot]=head[u],head[u]=tot;
to[++tot]=u,w[tot]=t,last[tot]=head[v],head[v]=tot;
return;
}
set<int>s;
void dfs(int u,int sum){
dis[u]=sum;vis[u]=1;pos[++sumpos]=u;
for(int i=head[u];i;i=last[i]){
int v=to[i];
if(!vis[v])dfs(v,sum^w[i]);
else s.insert(dis[v]^sum^w[i]);
}
return;
}
void solve(int ss){
s.clear();memset(d,0,sizeof(d));kd=0,sumpos=0;
dfs(ss,0);
set<int>::iterator it;
for(it=s.begin();it!=s.end();it++){
int x=*it;
for(int j=63;j>=0;j--)
if(x&(1ll<<j)){
if(d[j])x^=d[j];
else{
d[j]=x;
kd++;
break;
}
}
}
for(int j=0;j<=63;j++){
bool flag=0;
for(int l=0;l<=63;l++)if(d[l]&(1ll<<j)){flag=1;break;}
if(flag){
ans=(ans+sumpos*(sumpos-1)/2%mod*((1ll<<kd-1)%mod)%mod*((1ll<<j)%mod)%mod)%mod;
}
else{
int cnt=0;
for(int l=1;l<=sumpos;l++)
if(dis[pos[l]]&(1ll<<j))cnt++;
ans=(ans+cnt*(sumpos-cnt)%mod*((1ll<<kd)%mod)%mod*((1ll<<j)%mod)%mod)%mod;
}
}
return;
}
signed main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),t=read();
Dadd(u,v,t);
}
for(int i=1;i<=n;i++)
if(!vis[i])solve(i);
printf("%lld\n",ans);
return 0;
}