【集训】线性代数基础
题单
不是自己写的,总结的
P3812 【模板】线性基
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int D = 64;
ll w[D];
bool f;
bool ins(ll x) {
for (int i = D - 1; i >= 0; i--) {
if ((x >> i) & 1) {
if (w[i])
x ^= w[i];
else {
w[i] = x;
return true;
}
}
}
return false;
}
void simp() {
for (int i = D - 1; i >= 0; i--) {
if (w[i]) {
for (int j = D - 1; j > i; j--)
if ((w[j] >> i) & 1)
w[j] ^= w[i];
}
}
}
ll v[D];
ll mxk(ll k) {
int cnt = 0;
for (int i = 0; i < D; i++)
if (w[i] != 0)
v[cnt++] = w[i];
if (k >= (1ll << cnt))
return -1;
ll ans = 0;
for (int i = 0; i < cnt; i++)
if ((k >> i) & 1)
ans ^= v[i];
return ans;
}
int main() {
int n, m;
cin >> n;
for (int i = 1; i <= n; i++) {
ll x;
cin >> x;
f |= (!ins(x));
}
simp();
cin >> m;
for (int i = 1; i <= m; i++) {
ll k;
cin >> k;
if (f)
k--;
cout << mxk(k) << endl;
}
return 0;
}
LOJ114
题意:
我们可以把线性基进一步简化:对每个
- 所有数的最高位互不相同。
- 对每个
,如果存在某个 的最高位是第 位,则其它数的第 位必为 。
这样的一组线性基 ,其中 的最高位依次递增, 中所有元素从小到大排列后恰好就是: ,一直到 。那么第 k 小也就容易求出了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int D=64;
ll w[D];
bool f;
bool ins(ll x){
for(int i=D-1;i>=0;i--){
if((x>>i)&1){
if(w[i]) x^=w[i];
else{
w[i]=x;
return true;
}
}
}
return false;
}
void simp(){
for(int i=D-1;i>=0;i--){
if(w[i]){
for(int j=D-1;j>i;j--)
if((w[j]>>i)&1)
w[j]^=w[i];
}
}
}
ll v[D];
ll mxk(ll k){
int cnt = 0;
for (int i = 0; i < D; i++)
if (w[i] != 0)
v[cnt++] = w[i];
if (k >= (1ll << cnt))
return -1;
ll ans = 0;
for (int i = 0; i < cnt; i++)
if ((k >> i) & 1)
ans ^= v[i];
return ans;
}
int main(){
int n,m;
cin>>n;
for(int i=1;i<=n;i++){
ll x;
cin>>x;
f|=(!ins(x));
}
simp();
cin>>m;
for(int i=1;i<=m;i++){
ll k;
cin>>k;
if(f) k--;
cout<<mxk(k)<<endl;
}
return 0;
}
P4570 [BJWC2011] 元素
给出
只需要按价值从大到小考虑,每次能加入就加入即可。
可以用线性基来判断能否插入一个新的元素。时间复杂度
证明用一句话来说就是向量空间和它的线性无关子集构成拟阵;当然也可以使用归纳法。
#include <bits/stdc++.h>
using namespace std;
const int D=64,N=1005;
#define ll long long
ll w[D],n;
struct s{
ll a,b;
}a[N];
bool cmp(s a,s b){
return a.b>b.b;
}
bool ins(ll x){
for(int i=D-1;i>=0;i--){
if((x>>i)&1){
if(w[i]) x^=w[i];
else{
w[i]=x;
return true;
}
}
}
return false;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].a>>a[i].b;
}
sort(a+1,a+n+1,cmp);
ll ans=0;
for(int i=1;i<=n;i++){
if(ins(a[i].a)) ans+=a[i].b;
}
cout<<ans<<endl;
return 0;
}
P4151 [WC2011] 最大XOR和路径
给定一张
任取生成树
考虑任意一个
注意到当
因此这个式子就等于:
而当
于是,
另一方面,不管当前在哪个点
综上,我们证明了所有
那么对后者建出线性基,查询前者在后者中的最大异或值即可,总复杂度
#include <bits/stdc++.h>
using namespace std;
const int N=50005,M=200005,D=64;
#define ll long long
ll h[N],e[M],ne[M],W[M],idx;
ll f[N],w[D];
bool vis[N];
bool ins(ll x){
for(int i=D-1;i>=0;i--) {
if((x>>i)&1){
if(w[i]) x^=w[i];
else{
w[i]=x;
return true;
}
}
}
return false;
}
void simp(){
for(int i=D-1;i>=0;i--){
if(w[i]!=0){
for(int j=D-1;j>i;j--)
if((w[j]>>i)&1) w[j]^=w[i];
}
}
}
ll mx(ll x){
ll ans=x;
for (int i=D-1;i>=0;i--)
ans=max(ans,ans^w[i]);
return ans;
}
void add(int u,int v,ll w){
e[idx]=v,W[idx]=w,ne[idx]=h[u],h[u]=idx++;
}
void dfs(int u,ll ans){
f[u]=ans,vis[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!vis[j]) dfs(j,ans^W[i]);
else ins(ans^W[i]^f[j]);
}
}
int main(){
memset(h,-1,sizeof(h));
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++){
ll u,v,w;
cin>>u>>v>>w;
add(u,v,w);add(v,u,w);
}
dfs(1,0);
simp();
cout<<mx(f[n])<<endl;
return 0;
}
CF724G Xor-matic Number of the Graph
对于一对
直接做并不是很好做,考虑按位分开做:
- 对于线性基
和二进制位 ,有结论: - 设
中元素个数为 ,则 $B¥ 可以表示出 个不同的数。 - 如果
中存在二进制第 位为 的数,则那 个数中恰有 个数的二进制第 位为 ,另外 个数的二进制第 位为 。 - 如果
中不存在二进制第 位为 的数,显然不可能表示出二进制第 位为 的数,全部 个数的二进制第 位均为 。
可以通过组合恒等式 证明。
枚举二进制位
如果存在,这意味着无论
如果不存在,这意味着
这意味着
对于每个联通块分别计算即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
const int D=64,N=100005,M=N*4,P=1e9+7;
ll h[N],e[M],ne[M],W[M],idx;
ll w[D],f[N],s[N],C,t;
bool vis[N];
bool ins(ll x){
for(int i=60;i>=0;i--){
if((x>>i)&1){
if(w[i]) x^=w[i];
else{
w[i]=x;
++C;
return true;
}
}
}
return false;
}
void add(int u,int v,ll w){
e[idx]=v,W[idx]=w,ne[idx]=h[u],h[u]=idx++;
}
void dfs(int u,ll ans){
f[u]=ans,vis[u]=1,s[++t]=u;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(!vis[j]) dfs(j,ans^W[i]);
else ins(ans^W[i]^f[j]);
}
}
signed main(){
memset(h,-1,sizeof(h));
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++){
ll u,v,w;
cin>>u>>v>>w;
add(u,v,w),add(v,u,w);
}
ll ans=0;
for(int i=1;i<=n;i++){
if(vis[i]==0){
memset(w,0,sizeof(w));
C=t=0;
dfs(i,0);
for(int j=0;j<60;j++){
ll c=(1ll<<j)%P;
bool ok=0;
for(int k=60;k>=0;k--) if((w[k]>>j)&1) ok=1;
if(ok) ans=(ans+(ll)t*(t-1)/2%P*((1ll<<C-1)%P)%P*c)%P;
else{
int x=0;
for(int i=1;i<=t;i++) if((f[s[i]]>>j)&1) ++x;
ans=(ans+(ll)x*(t-x)%P*((1ll<<C)%P)%P*c)%P;
}
}
}
}
cout<<ans%P<<endl;
return 0;
}
P10682 [COTS 2024] 奇偶南瓜 Tikvani
给定
对每个起点
即对于一条非树边
对于任意一条路径,找到其中的第一条非树边
那么此时总共只有 bitset
优化高斯消元维护。
时间复杂度
#include<bits/stdc++.h>
using namespace std;
const int N=405,P=1e9+7;
int n,m;
struct s{int v,id;};
vector<s> G[N];
bitset <N> w,d[N],x[N];
bool vis[N];
void ins() {
for(int i=1;i<=m;++i) if(w[i]) {
if(!x[i].any())
return x[i]=w,void();
else w^=x[i];
}
}
void dfs(int u) {
vis[u]=true;
for(auto e:G[u]) {
if(!vis[e.v]) d[e.v]=d[u],d[e.v].set(e.id),dfs(e.v);
else w=d[u],w^=d[e.v],w.flip(e.id),ins();
}
}
signed main() {
cin>>n>>m;
for(int i=1,u,v;i<=m;++i) cin>>u>>v,G[u].push_back({v,i});
for(int i=1;i<=n;++i) {
for(int u=1;u<=n;++u)
d[u].reset(),vis[u]=false;
dfs(i);
}
int ans=1;
for(int i=1;i<=m;++i) if(!x[i].any()) ans=ans*2%P;
cout<<ans<<endl;
return 0;
}
与数据结构配合的线性基
- 线性基插入的复杂度是
。 - 线性基合并的复杂度是
,方法是把其中一个的所有元素插入到另一边。
CF1100F Ivan and Burgers
给一个长为
在一些数中取若干个数,要求异或和最大,不难想到线性基。
直接线段树维护区间,则复杂度达到
考虑离线,然后分治。每次考虑解决经过当前区间中点的询问。
对于一个区间,我们处理出中点mid往左的后缀线性基、往右的前缀线性基,则可以在
然后,对于两端点都在左边的区间,往左递归处理;两端点都在右边的区间,往右递归处理。
分治时间复杂度:
#include <bits/stdc++.h>
using namespace std;
const int N=500005;
struct ss{
int s[20];
void ins(int x){
if(x)
for(int i=19;i>=0;i--)
if(x>>i&1){
if(!s[i]) {s[i]=x;break;}
x^=s[i];
}
}
void clear(){
memset(s,0,sizeof(s));
}
}d[N];
int merge(ss a,ss b){
for(int i=19;i>=0;i--) a.ins(b.s[i]);
int res=0;
for(int i=19;i>=0;i--)
res=max(res,res^a.s[i]);
return res;
}
int a[N],n,q[N],qL[N],qR[N],ans[N],m;
void solve(int l,int r,int L,int R){
if(L>R) return;
if(l==r){
for(int i=L;i<=R;i++) ans[q[i]]=a[l];
return;
}
static int tmpL[N],tmpR[N];
int tL=0,tR=0,mid=l+r>>1;
d[mid].clear();d[mid].ins(a[mid]);
for(int i=mid-1;i>=l;i--) (d[i]=d[i+1]).ins(a[i]);
for(int i=mid+1;i<=r;i++) (d[i]=d[i-1]).ins(a[i]);
for(int i=L;i<=R;i++){
if(qL[q[i]]<=mid){
if(qR[q[i]]>mid) ans[q[i]]=merge(d[qL[q[i]]],d[qR[q[i]]]);
else tmpL[++tL]=q[i];
}
else tmpR[++tR]=q[i];
}
for(int i=1;i<=tL;i++)q[L+i-1]=tmpL[i];
for(int i=1;i<=tR;i++)q[L+tL+i-1]=tmpR[i];
solve(l,mid,L,L+tL-1);
solve(mid+1,r,L+tL,L+tL+tR-1);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
cin>>m;
for(int i=1;i<=m;i++)cin>>qL[i]>>qR[i],q[i]=i;
solve(1,n,1,m);
for(int i=1;i<=m;i++)cout<<ans[i]<<endl;
return 0;
}
P3292 [SCOI2016] 幸运数字
按照倍增的思想,
#include<bits/stdc++.h>
using namespace std;
const int M=20005,D=64;
typedef long long ll;
ll a[100005],n;
struct s{
ll w[64];
s(){ memset(w,0,sizeof(w)); }
void insert(ll x){
for(int i=D-1;i>=0;i--){
if(!((x>>i)&1)) continue;
if(!w[i]){w[i]=x;return ;}
x^=w[i];
}
}
ll mx(){
ll ans=0;
for(int i=D-1;i>=0;i--)
ans=max(ans,ans^w[i]);
return ans;
}
};
s merge(s u,s v){
for(int i=D-1;~i;i--)
if(v.w[i]) u.insert(v.w[i]);
return u;
}
int idx,ne[2*M],h[2*M],e[2*M];
void add(int x,int y){
e[++idx]=y,ne[idx]=h[x],h[x]=idx;
}
int f[M][21],dep[M];
s g[M][21];
void dfs(int u,int fa){
f[u][0]=fa;
g[u][0].insert(a[fa]);
dep[u]=dep[fa]+1;
for(int i=h[u];i;i=ne[i]){
int to=e[i];
if(to!=fa) dfs(to,u);
}
}
ll query(int x,int y){
if(dep[x]>dep[y])swap(x,y);
s t;
t.insert(a[x]),t.insert(a[y]);
for(int i=20;i>=0;i--){
if(dep[f[y][i]]>=dep[x]) t=merge(t,g[y][i]),y=f[y][i];
}
if(x==y)return t.mx();
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]){
t=merge(t,g[x][i]);
t=merge(t,g[y][i]);
x=f[x][i],y=f[y][i];
}
}
t=merge(t,g[x][0]);
return t.mx();
}
int main(){
int q;
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
add(x,y),add(y,x);
}
dfs(1,0);
for(int j=1;j<=20;j++){
for(int i=1;i<=n;i++){
f[i][j]=f[f[i][j-1]][j-1];
g[i][j]=merge(g[f[i][j-1]][j-1],g[i][j-1]);
}
}
while(q--){
int x,y;
cin>>x>>y;
cout<<query(x,y)<<endl;
}
return 0;
}
P5607 [Ynoi2013] 无力回天 NOI2017
由于要求维护区间信息,采用线段树维护区间线性基。
考虑差分,将区间修改转化为两个单点修改。接下来考虑如何在差分后的序列上求答案。
序列
不难发现,对于
查询时,求出
线段树上合并两个线性基复杂度
#include <bits/stdc++.h>
#define L(u) (u<<1)
#define R(u) (u<<1|1)
#define mid ((l+r)>>1)
using namespace std;
const int N = 50050;
struct XOR {
int d[30], s;
void init() {memset(d, 0, sizeof d);}
void insert(int x) {
for(int i = 29; i >= 0; --i)
if((x >> i) & 1) {
if(d[i]) x ^= d[i];
else {d[i] = x; break;}
}
}
} t[N << 2]; int a[N], n, m;
XOR merge(XOR a, XOR b) {
XOR res; res.init();
for(int i = 29; i >= 0; --i) if(a.d[i]) res.d[i] = a.d[i]; else res.d[i] = b.d[i];
for(int i = 29; i >= 0; --i) if(a.d[i] && b.d[i]) res.insert(b.d[i]);
res.s = a.s ^ b.s; return res;
}
void P(int u) {t[u] = merge(t[L(u)], t[R(u)]);}
void build(int u, int l, int r) {
if(l == r) {t[u].insert(t[u].s = a[l]); return;}
build(L(u), l, mid); build(R(u), mid+1, r); P(u);
}
void modify(int u, int l, int r, int p, int x) {
if(l == r) {t[u].init(), t[u].insert(t[u].s ^= x); return; }
p <= mid ? modify(L(u), l, mid, p, x) : modify(R(u), mid+1, r, p, x); P(u);
}
int finds(int u, int l, int r, int p) {
return l == r ? t[u].s : (p <= mid ? finds(L(u), l, mid, p) : finds(R(u), mid+1, r, p) ^ t[L(u)].s);
}
XOR findi(int u, int l, int r, int tl, int tr) {
if(tl <= l && r <= tr) return t[u];
if(tl > mid) return findi(R(u), mid+1, r, tl, tr);
if(tr <= mid) return findi(L(u), l, mid, tl, tr);
return merge(findi(L(u), l, mid, tl, tr), findi(R(u), mid+1, r, tl, tr));
}
int main() {
cin>>n>>m;
for(int i = 1; i <= n; ++i) cin>>a[i];
for(int i = n-1; i; --i) a[i+1] ^= a[i];
build(1, 1, n);
for(int opt, l, r, x; m--; ) {
cin>>opt>>l>>r>>x;
if(opt == 1) {
modify(1, 1, n, l, x);
if(r < n) modify(1, 1, n, r+1, x);
} else {
int tmp = finds(1, 1, n, l);
if(l == r) cout<<max(x, x^tmp)<<endl;
else {
XOR t = findi(1, 1, n, l+1, r); t.insert(tmp);
for(int i = 29; i >= 0; --i) x = max(x, x ^ t.d[i]);
cout<<x<<endl;
}
}
}
}
P3389 【模板】高斯消元法
#include<bits/stdc++.h>
using namespace std;
int n,pl;
double a[1001][1001];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n+1;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++){
pl=i;
for(int j=i;j<=n;j++)
if(fabs(a[j][i])>fabs(a[pl][i])) pl=j;
if(a[pl][i]==0) {cout<<"No Solution";return 0;}
for(int j=1;j<=n+1;j++)
swap(a[i][j],a[pl][j]);
double k=a[i][i];
for(int j=1;j<=n+1;j++)
a[i][j]=a[i][j]/k;
for(int j=1;j<=n;j++){
if(i!=j){
double ki=a[j][i];
for(int m=1;m<=n+1;m++)
a[j][m]=a[j][m]-ki*a[i][m];
}
}
}
for(int i=1;i<=n;i++)
printf("%.2lf\n",a[i][n+1]);
return 0;
}
P2447 [SDOI2010] 外星千足虫
一种方法是直接使用线性基:方程组有唯一解相当于
所有方程插入线性基即可,时间复杂度
另一方面,我们也可以使用高斯消元,不过在消元的时候尽可能选编号更小的方程来进行操作;这本质上和线性基是相同的,相当于快速找到了下一次线性
基发生改变的位置。
#include <bits/stdc++.h>
using namespace std;
char s[1010];
bitset<1010> a[2010];
int f(int n, int m){
int ans = -1;
for (int i = 1; i <= n; i++){
int cur = i;
while (cur <= m && !a[cur].test(i)) cur++;
if (cur > m) return 0;
ans = max(ans, cur);
if (cur != i)swap(a[cur], a[i]);
for (int j = 1; j <= m; j++)
if (i != j && a[j].test(i))
a[j] ^= a[i];
}
return ans;
}
int main(){
int n, m;
cin>>n>>m;
for (int i = 1, res; i <= m; i++){
cin>>s>>res;
for (int j = 0; j < n; j++)
a[i].set(j+1,s[j]=='1');
a[i].set(0, res);
}
int ret = f(n, m);
if (ret){
cout<<ret<<endl;
for (int i = 1; i <= n; i++)
cout<<(a[i].test(0) ? "?y7M#" : "Earth") <<endl;
}
else cout<<"Cannot Determine\n"<<endl;
return 0;
}
CF24D Broken robot
注意,
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <iomanip>
using namespace std;
const int N = 1010;
int n, m;
int x, y;
double f[N][N];
double a[N][N];
void gauss()
{
for (int i = 1; i <= m; i ++ )
{
double t = a[i + 1][i] / a[i][i];
int d[3] = {i, i + 1, m + 1};
for (int j = 0; j < 3; j ++ )
a[i + 1][d[j]] -= t * a[i][d[j]];
a[i + 1][i] = 0;
}
for (int i = m; i; i -- )
{
a[i - 1][m + 1] -= a[i - 1][i] / a[i][i] * a[i][m + 1];
a[i - 1][i] = 0;
}
}
int main()
{
cin >> n >> m;
cin >> x >> y;
if (m == 1) printf("%.4lf\n", 2.0 * (n - x));
else
{
for (int i = n - 1; i >= x; i -- )
{
a[1][1] = 2.0 / 3, a[1][2] = -1.0 / 3, a[1][m + 1] = f[i + 1][1] / 3 + 1;
a[m][m] = 2.0 / 3, a[m][m - 1] = -1.0 / 3, a[m][m + 1] = f[i + 1][m] / 3 + 1;
for (int j = 2; j < m; j ++ )
{
a[j][j - 1] = -1.0 / 4, a[j][j] = 3.0 / 4, a[j][j + 1] = -1.0 / 4;
a[j][m + 1] = f[i + 1][j] / 4 + 1;
}
gauss();
for (int j = 1; j <= m; j ++ ) f[i][j] = a[j][m + 1] / a[j][j];
}
cout.setf(std::ios::fixed);
cout << setprecision(4) << f[x][y];
//cout << f[x][y];
}
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!