线性代数
vjudge link
cnblog link
向量
向量线性相关:两条向量在同一直线上,此时变换完的空间会被压少若干维。反之则不是线性相关。
线性变换:经过一个
秩(rank):行列式在空间上形成的维度,或是高斯消元时的主元(非 0)个数,即他们线性无关。
貌似下面有一些地方把 基向量 写成 基向量 了,请仔细甄别。
矩阵乘法
若现在有两个矩阵
for(int i = 1; i <= 2; i++)
for(int j = 1; j <= 2; j++)
for(int k = 1; k <= 2; k++)
res.a[i][j] += a[i][k] * b.a[k][j];
请自行意会。具体就是
几何意义:
先定义基向量是一个
拓展到
对于原空间上的一个向量
现在一个向量集合
其实了解这个几何意义貌似没有什么用处,大抵是可以和向量一起理解,以及这是了解矩阵乘法是如何推出的。
实际应用上,大多都是用一个
考虑何时会降维,当有一个基向量和其他若干基向量线性相关时,那么这个基向量就可以被其他的这些基向量表示出来,也就相当于这个基向量对于维数是没有作用的。但是本来就只有
m 个基向量也同时有m 个维度,损失一个基向量就代表着你会损失这一个维度,也就产生了降维。
应用
这个有啥用呢,可以快速处理递推的第
比如我们现在有一个斐波那契数列
那么我们尝试把它丢到矩阵上,就是我们现在有的向量是
我们想推出
那么我们现在的基向量就是
因为
然后要补上新向量的
那么就补完了,将
那么 ans.a[1][1] 就是
summary: 其实就是第
::::info[code]
注意这里的
#include <bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
#define pb push_back
const int N = 5e3 + 10, M = 2e2 + 10, inf = 1e18, mod = 1e9 + 7;
using namespace std;
typedef pair<int, int> pii;
struct Matr
{
int a[3][3];
Matr() { memset(a, 0, sizeof a); }
Matr operator * (const Matr &b) const
{
Matr res;
for(int i = 1; i <= 2; i++)
for(int j = 1; j <= 2; j++)
for(int k = 1; k <= 2; k++)
(res.a[i][j] += a[i][k] * b.a[k][j]) %= mod;
return res;
}
};
Matr mpower(Matr a, int b)
{
Matr ans; ans.a[1][1] = ans.a[2][2] = 1;
while(b)
{
if(b & 1) ans = ans * a;
a = a * a, b >>= 1;
}
return ans;
}
int n;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
if(n <= 2) return cout << "1\n", 0;
Matr A; A.a[1][1] = A.a[1][2] = A.a[2][1] = 1;
cout << mpower(A, n - 1).a[1][1] << "\n";
return 0;
}
/*
*/
::::
AT_dp_r
设
欸你发现,这个 dp 如果去掉
::::info[code]
#include <bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
#define pb push_back
const int N = 1e2 + 10, M = 2e2 + 10, inf = 1e18, mod = 1e9 + 7;
using namespace std;
typedef pair<int, int> pii;
int n, K;
struct Matr
{
int a[N][N];
Matr() { memset(a, 0, sizeof a); }
Matr operator * (Matr &b) const
{
Matr res;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
for(int k = 1; k <= n; k++)
(res.a[i][j] += a[i][k] * b.a[k][j] % mod) %= mod;
return res;
}
};
Matr A;
Matr mpower(Matr a, int b)
{
Matr res;
for(int i = 1; i <= n; i++) res.a[i][i] = 1;
while(b)
{
if(b & 1) res = res * a;
a = a * a, b >>= 1;
}
return res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> K;
for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) cin >> A.a[i][j];
Matr ans = mpower(A, K);
int sum = 0;
for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) (sum += ans.a[i][j]) %= mod;
cout << sum << "\n";
return 0;
}
/*
dp_{i,j,t}=\sum_{k=1}^{n} dp_{i,k,t-1} \times dp_{k,j,1}
*/
::::
P6569
蓝的码字了,直接贴 Reynard 的:
::::info[code]
#include <bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
#define pb push_back
const int N = 1e2 + 10, M = 2e2 + 10, inf = 1e18, mod = 2009;
using namespace std;
typedef pair<int, int> pii;
int n, K;
struct Matr
{
int a[N][N];
Matr() { memset(a, 0, sizeof a); }
Matr operator * (Matr &b) const
{
Matr res;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
for(int k = 1; k <= n; k++)
(res.a[i][j] ^= a[i][k] * b.a[k][j]);
return res;
}
};
struct Vec
{
int a[N];
Vec() { memset(a, 0, sizeof a); }
};
Vec operator * (Matr &b, Vec &a)
{
Vec res;
for(int j = 1; j <= n; j++)
for(int k = 1; k <= n; k++)
(res.a[j] ^= a.a[k] * b.a[k][j]);
return res;
}
Matr A;
Matr mpower(Matr a, int b)
{
Matr res;
for(int i = 1; i <= n; i++) res.a[i][i] = 1;
while(b)
{
if(b & 1) res = res * a;
a = a * a, b >>= 1;
}
return res;
}
int m, q, a[N];
Matr g[33];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m >> q;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1, x, y; i <= m; i++) cin >> x >> y, g[0].a[x][y] = g[0].a[y][x] = 1;
for(int i = 1; i < 32; i++) g[i] = g[i - 1] * g[i - 1];
for(int t = 1, x; t <= q; t++)
{
cin >> x;
Vec f;
for(int i = 1; i <= n; i++) f.a[i] = a[i];
for(int i = 0; i < 32; i++) if((x >> i) & 1) f = g[i] * f;
cout << f.a[1] << "\n";
}
return 0;
}
/*
*/
::::
广义的矩阵乘法
综上我们可以定义广义矩阵乘法为:
对于矩阵
其中需要满足:
常见的
高斯消元
逆矩阵:定义
其实没太听懂。本质上是模拟做一个加减消元求解线性方程组的过程。
高斯消元从左到右处理每一列,每次尝试在当前列找到一个主元。
主元:当前列中选出的一个非零元素,用它消掉其他行在这一列的系数。
设有m个方程、n个未知数,构造增广矩阵
- 从左到右枚举变量列;
- 在当前还没有处理的行中,寻找这一列的非零元素作为主元;
- 如果找到了主元,将主元所在行交换到当前行;
- 将主元归一化,并用主元消去其他行在这一列的系数;
- 进入下一行,继续处理后面的列;
- 消元结束后判断是否存在矛盾行,并根据主元个数判断解的类型;
- 若需要求具体解,则根据主元列和自由变量进行回代。
其实就是在每一列中确定一个主元让它的系数控制为
1 ,那么它需要变为1 的话要把它同行的元素同比进行放缩,同列的元素进行加减。就是在它这一列只保留下它自己为1 。
若这一列能找到主元,那么这个维度上的向量就与其他向量线性无关,换句话说就是方程组的秩(rank)可以
那么这个就是原始的高斯消元,最后可以形成一个右上角有数的一个矩阵,如:
此时右下角的
还存在更高级的高斯-约旦消元。
不仅消去主元下面的元素,也消去主元上面的元素。最后左边接近单位矩阵,可以直接读出答案。
意思就是不只是消下面的行,上面的行也一起消,最后可以形成:
其中
::::info[code]
#include <bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
#define pb push_back
const int N = 1e2 + 10, M = 2e2 + 10, inf = 1e18, mod = 2009;
const double eps = 1e-6;
using namespace std;
typedef pair<int, int> pii;
int n;
double a[N][N];
int rnk; // 秩
void gauss()
{
vector<int> where(n + 1);
for(int col = 1; col <= n; col++) // 枚举的是列
{
int p = rnk + 1;
while(p <= n && fabs(a[p][col]) < eps) p++; // 找当前列的非 0 元素
if(p > n) continue;
swap(a[p], a[++rnk]); where[col] = rnk;
double pivot = a[rnk][col]; // 先存下主元的系数,不然一会会被直接除成 0
for(int j = col; j <= n + 1; j++) a[rnk][j] /= pivot; // 这里枚举的是列
for(int i = 1; i <= n; i++) // Jordan 直接全部删完 现在枚举的是行
{
if(i == rnk || fabs(a[i][col]) < eps) continue;
double t = a[i][col];
for(int j = col; j <= n + 1; j++) a[i][j] -= t * a[rnk][j];
}
}
for(int i = 1; i <= n; i++)
{
bool zro = 1;
for(int j = 1; j <= n; j++) if(fabs(a[i][j]) > eps) { zro = 0; break; }
if(zro && fabs(a[i][n + 1]) > eps) return cout << "-1", void();
}
if(rnk != n) return cout << "0\n", void();
for(int i = 1; i <= n; i++)
{
cout << "x" << i << "=";
// cout << where[i] << "\n";
cout << fixed << setprecision(10) << a[where[i]][n + 1] << "\n";
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) for(int j = 1; j <= n + 1; j++) cin >> a[i][j];
gauss();
return 0;
}
/*
*/
::::
更高级的模板
要求输出方程组
我们之前求出来的秩是线性无关的主元个数,但是要输出的是维度,维度就是 解可以自由移动的空间。自由移动,那自然就是自由元造成的。自由元可以任意取值,能造成解的移动,那么第一问输出
一组特解就和上面那个是一样的。每一个维度上的基就是把这一维上的自由元设为
::::info[code]
#include <bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
#define pb push_back
const int N = 1e3 + 10, M = 2e2 + 10, inf = 1e18, mod = 998244353;
const double eps = 1e-6;
using namespace std;
typedef pair<int, int> pii;
int n, m, a[N][N];
int power(int a, int b)
{
int ans = 1;
while(b)
{
if(b & 1) ans = ans * a % mod;
a = a * a % mod, b >>= 1;
}
return ans;
}
int rnk;
void gauss()
{
vector<int> where(m + 1);
for(int col = 1; col <= m && rnk < n; col++)
{
int p = rnk + 1;
while(p <= n && a[p][col] == 0) p++;
if(p > n) continue;
swap(a[p], a[++rnk]); where[col] = rnk;
int inv = power(a[rnk][col], mod - 2);
for(int j = col; j <= m + 1; j++) (a[rnk][j] *= inv) %= mod;
for(int i = 1; i <= n; i++)
{
if(i == rnk || a[i][col] == 0) continue;
int t = a[i][col];
for(int j = col; j <= m + 1; j++) (a[i][j] = a[i][j] - t * a[rnk][j] % mod + mod) %= mod;
}
}
for(int i = 1; i <= n; i++)
{
bool zro = 1;
for(int j = 1; j <= m; j++) zro &= (a[i][j] == 0);
if(zro && a[i][m + 1] != 0) return cout << "-1\n", void();
}
cout << m - rnk << "\n";
for(int i = 1; i <= m; i++) cout << a[where[i]][m + 1] << " ";
cout << "\n";
for(int i = 1; i <= m; i++)
{
if(where[i]) continue;
for(int j = 1; j <= m; j++)
{
if(i == j) cout << 1 << " ";
else cout << (-a[where[j]][i] + mod) % mod << " ";
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) cin >> a[i][j];
for(int i = 1; i <= n; i++) cin >> a[i][m + 1];
gauss();
return 0;
}
/*
*/
::::
线性基
感觉有点懂了。
线性基维护的是一个集合
具体就是对于每一位只留下一个有价值的数。就比如第
因为每个位只有
想对于每一位只留下一个有价值的数,定义
这样就构造出来了一个线性基,每一个
bool ins(int x) // 低配线性基,为普通高斯消元版,现在每一位是一个int
{
for(int i = 60; ~i; i--) if((x >> i) & 1ll)
{
if(p[i]) x ^= p[i];
else
{
rnk++, p[i] = x;
return 1;
}
}
return 0;
}
现在看看她为什么是对的。先考虑一个感性理解:若
或者再来一个严谨一点的。线性基,就是线性变换中的一个基。将每个数变成二进制后理解为一个线性方程组,每个元的系数只有
考虑高斯消元的一个过程,在第
那么
getmax
找线性基里面的最大值。因为每一位的选择一定会让
getmin
若线性基里面可以合成出
板子
::::info[板子code]
约旦写法貌似有点局限性(?
#include <bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
#define pb push_back
const int N = 1e6 + 10, M = 2e2 + 10, inf = 1e18, mod = 2009;
const double eps = 1e-6;
using namespace std;
typedef pair<int, int> pii;
int n, a[60];
struct LB
{
int p[65], rnk;
LB() { memset(p, 0, sizeof p); rnk = 0; }
bool ins(int x) // 低配线性基,为普通高斯消元版,现在每一位是一个int
{
for(int i = 60; ~i; i--) if((x >> i) & 1ll)
{
if(p[i]) x ^= p[i];
else
{
rnk++, p[i] = x;
return 1;
}
}
return 0;
}
bool ins(int x) // 约旦线性基,每一位只有 2^B
{
for(int i = 60; ~i; i--) if((x >> i) & 1ll)
{
if(p[i]) x ^= p[i];
else
{
for(int j = i - 1; ~j; j--) if((x >> j) & 1ll) x ^= p[j];
rnk++, p[i] = x;
for(int j = 60; j != i; j--) if((p[j] >> i) & 1) p[j] ^= x;
return 1;
}
}
return 0;
}
int get_max()
{
int res = 0;
for(int i = 60; ~i; i--) res ^= p[i];
return res;
}
}b;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i], b.ins(a[i]);
cout << b.get_max() << "\n";
return 0;
}
/*
*/
::::
同样的,高斯消元也可以使用高斯-约旦。具体就是看之前插进线性基中的数在我现在这一位是否为
bool ins(int x) // 约旦线性基,每一位只有 2^B
{
for(int i = 60; ~i; i--) if((x >> i) & 1ll)
{
if(p[i]) x ^= p[i];
else
{
for(int j = i - 1; ~j; j--) if((x >> j) & 1ll) x ^= p[j];
rnk++, p[i] = x;
for(int j = 60; j != i; j--) if((p[j] >> i) & 1) p[j] ^= x;
return 1;
}
}
return 0;
}
约旦写法和普通写法一样,若返回
其实你发现,约旦写法单次插入是
ok 我感知完了,每次都约旦一下确实有点拉了,可以在最后做一遍 standard 来消掉。
void standard()
{
for(int i = 0; i <= 61; i++) if(p[i])
for (int j = i - 1; j >= 0; --j)
if (p[i] >> j & 1) p[i] ^= p[j];
}
全局异或第 k 大/小
你发现做了约旦版的线性基后,异或的所有情况就有
inline int get_min(int k)
{
int res = 0;
vector<int> v;
for(int i = 0; i <= 61; i++) if(p[i]) v.pb(p[i]);
for(int i = 0; i < v.size(); i++) if((k >> i) & 1) res ^= v[i];
return res;
}
P4570
你发现她要求的是选出的元素全部线性无关,那么若一个更优的元素没有被选到,则可以通过调整前面的元素来得到,因此将
::::info[code]
#include <bits/stdc++.h>
#define int long long
#define mk make_pair
#define fi first
#define se second
#define pb push_back
const int N = 1e6 + 10, M = 2e2 + 10, inf = 1e18, mod = 2008;
const double eps = 1e-6;
using namespace std;
typedef pair<int, int> pii;
int n, ans;
pii a[N];
struct LB
{
int p[65], rnk;
LB() { memset(p, 0, sizeof p); rnk = 0; }
bool ins(int x)
{
for(int i = 60; ~i; i--) if((x >> i) & 1ll)
{
if(p[i]) x ^= p[i];
else return ++rnk, p[i] = x, 1;
}
return 0;
}
}b;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i].se >> a[i].fi;
sort(a + 1, a + n + 1);
for(int i = n; i; i--) // v 从大到小拿
if(b.ins(a[i].se)) ans += a[i].fi;
cout << ans << "\n";
return 0;
}
/*
*/
::::