看到一篇非常好的博客:传送门!!!
看了以后大概就学会线性基了,向大佬%%%。
蒟蒻再根据自己的理解讲一讲线性基:
线性基在百度和别人博客上基本上看不懂~
就是用有限的数集合{s}代表无限的数集合{k}。具体的代表方式就是它们能组合出的异或值相同。而有限的数具体是多少呢?最大数二进制下的位数。
对于常见的oi题目,线性基就是62个数的集合。
线性基的性质在于它的第 i i 个数二进制下第 i i 位是1。
所以线性基大概长成这样:
1****
-1***
–1**
—1*
—-1
“-”表示没填,“1”表示这一位是1,“*”表示这一位填1或0。
当然,线性基不一定是满的,有可能某一位会没有,如果是满的,那就可以异或出所有的数。所以基本上不会是满的。
线性基的主要思想是贪心,那么怎么构造线性基呢?
假设我们现在已经有了一组线性基,我们在线性基里判断数 r r 能否被异或出来。如果r的二进制最高位是k,那么找线性基里的第 k k 个数,由于再向下找所有的第 k k 位都为0了,所以要组成 r r 必须异或这个数,于是我们把 r r 异或这个数的答案再在线性基中匹配。反之,若线性基中没有第 k k 个数,那么直接把 r r 变成线性基中第 k k 个数。
有人要问有可能第 k k 个数向上有可能第 k k 位为1,为什么不异或它们呢?很显然,如果异或它们,就会导致更高位为1,而这个1是不能被异或消掉的。
遗憾的是,线性基合并只能暴力把一个线性基加入另一个里。
线性基还有一些性质:
然后可以看一看开头那篇博客的例题。
贴一下我的代码:
注意0的存在
#include
#include
#include
using namespace std;
int n,q,T,cnt;
long long p[65];
long long rk[65];
long long x,k;
void add(long long a)
{
for(int i=62;i>=0;i--)
{
if(((a>>i)&1)==1)
{
if(!p[i]){p[i]=a;break;}
else a=a^p[i];
}
if(a==0) break;
}
}
int main()
{
scanf("%d",&T);
int W=T;
while(T--)
{
memset(p,0,sizeof(p));
memset(rk,0,sizeof(rk));
cnt=-1;
scanf("%d",&n);
printf("Case #%d:\n",W-T);
for(int i=1;i<=n;i++)
scanf("%lld",&x),add(x);
for(int i=62;i>0;i--)
for(int j=i-1;j>=0;j--)
if(((p[i]>>j)&1)==1&&p[j])
p[i]=p[i]^p[j];
for(int i=0;i<=62;i++)
if(p[i])
rk[++cnt]=p[i];
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%lld",&k);
long long ans=0;
if(cnt!=n-1) k--;
if(k>>cnt+1!=0)
{
printf("-1\n");
continue;
}
for(int j=cnt;j>=0;j--)
if(((k>>j)&1)==1)
ans=ans^rk[j];
printf("%lld\n",ans);
}
}
}
这道题一开始用高斯消元做的,后来发现其实高斯消元就是维护的线性基,就直接贴高斯消元的代码了。
#include
#include
#include
using namespace std;
struct lxy{
int next,to;
long long len;
}b[2000005];
int n,m,head[500005],cnt,_cnt;
long long a[500005];
long long dis[500005];
bool vis[500005];
long long ans,pos;
void add(int op,int ed,long long len)
{
b[++cnt].next=head[op];
b[cnt].to=ed;
b[cnt].len=len;
head[op]=cnt;
}
void dfs(int u)
{
vis[u]=1;
for(int i=head[u];i!=-1;i=b[i].next)
{
if(vis[b[i].to]==1)
a[++_cnt]=(dis[b[i].to]^dis[u]^b[i].len);
else
{
dis[b[i].to]=(dis[u]^b[i].len);
dfs(b[i].to);
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) head[i]=-1;
for(int i=1;i<=m;i++)
{
int x,y;long long z;
scanf("%d%d%lld",&x,&y,&z);
if(x!=y) add(x,y,z),add(y,x,z);
}
dfs(1);ans=dis[n];
for(int i=62;i>=0;i--)
{
pos=(1LL<int j=1;
while(((a[j]&pos)==0||vis[j]==1)&&j<=_cnt) j++;
if(j>_cnt) continue;
vis[j]=1;
if((ans&pos)==0) ans=(ans^a[j]);
for(int k=1;k<=_cnt;k++)
if(vis[k]==0&&(a[k]&pos)==pos)
a[k]=(a[k]^a[j]);
}
printf("%lld",ans);
}
给出一个无向图,求所有不同的三元组(u,v,s)的s之和 表示u到v的路径异或和为s。
这道题很有意思,它利用了异或二进制位上互不干扰的性质。每一位分别统计答案。
我个人的想法(不知道对不对,没打代码)是:
首先各联通块分别处理。把环加入线性基中。在dfs一下,统计有多少点对,假设线性基中有 n n 个数,那么第 i i 位的贡献就是 i∗2n−1 i ∗ 2 n − 1 。
证明:假设我们按上一道题的方式进行处理后。对于简单路径异或值第 i i 位是1的,不异或上第 i i 个,剩下随便选。对于简单路径异或值第 i i 位是0的,异或上第 i i 个,剩下随便选。