算法竞赛进阶指南0x35高斯消元与线性空间
高斯消元
高斯消元对应的矩阵有两种:
- 常规的线性方程组
- 异或操作(不需要乘上一个数再相减,直接异或即可)
概念理解起来不太费力,重点是代码实现。
ACWing207. 球形空间产生器(点击访问)
这道题目重点是考察解线性方程组(不太好用暴力来进行解题)
使用解线性方程组来进行求解
求解思路
代码
#include <bits/stdc++.h>
using namespace std;
double a[20][20];
double c[20][20];
double b[20];
const float zero = 1e-8;
int main()
{
int n;
//扫描数据
cin >> n;
for(int i = 0; i <= n; i++)
for(int j = 1; j <= n; j++)
scanf("%lf", &a[i][j]);
//把其他的与第一个进行相减,然后得到线性增广炬阵(c是系数矩阵,b是增广炬阵)
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++) c[i][j] = 2 * a[i][j] - 2 * a[0][j];
for(int j = 1; j <= n; j++) b[i] += a[i][j] * a[i][j] - a[0][j] * a[0][j];
}
//进行消元
for(int i = 1; i <= n; i++)//第i个变量
{
int pos = i;
while(pos < n && fabs(c[pos][i]) < zero) pos ++;
for(int j = 1; j <= n; j++) swap(c[pos][j], c[i][j]);
swap(b[pos], b[i]);
for(int j = 1; j <= n; j++)
{
if(j == pos) continue;
if(fabs(c[j][i]) > zero)
{
double factor = c[j][i] / c[pos][i];
for(int k = 1; k <= n; k++)
{
c[j][k] -= factor*c[pos][k];
}
b[j] -= factor * b[pos];
}
}
}
for(int i = 1; i <= n; i++)
{
double ans = b[i] / c[i][i];
printf("%.3lf ", ans);
fflush(stdout);
}
return 0;
}
ACWing208. 开关问题(点击访问)
思路
暴力枚举显然是不行。
如果直接取思考解决问题的方法,不太可能。这时候应该与数学相联系。
- 开关的状态只有0,1两种
- 开关打开关闭状态可以用0或者1来进行表示,同时,操作也可以这样。
同时,容易发现:最终操作的结果与按压开关的次序并没有关系。
可以存放一个矩阵
(\(A[i][j]==1,则操作第i个开关会影响第j个开关。特别让A[i][i]==1\))
根据上面的矩阵,操作某一个开关,这个开关可以看做是一个代号,转化为操作矩阵里面的对应元素。
回顾:(依据矩阵自由元的个数,还可以判断具体有多少种情况)
在矩阵中,如果最终的系数矩阵是单位矩阵,说明有一种情况。
如果有一行系数矩阵全部为0,但是这一行的常数矩阵是1,那么就无解
如果有m行全部都是0,那么就有m个自由变元(最终解的个数就是\(2^m\))
代码
#include <bits/stdc++.h>
using namespace std;
int matrix[40];
void Init()
{
memset(matrix, 0, sizeof(matrix));
}
inline int col(int x)
{
return 1 << x;
}
int main()
{
int T;
cin >> T;
while(T--)
{
Init();//一定不要忘记初始化
int cnt = 0;
bool logical = true;
int n;
cin >> n;
for(int k = 1; k <= 2; k++)
for(int i = 1; i <= n; i++)
{
int tmp = 0;
scanf("%d", &tmp);
matrix[i] ^= tmp;
}
while(1)
{
int x, y;
scanf("%d%d", &x, &y);
if(!(x||y)) break;
matrix[y] = matrix[y] | col(x);
}
for(int i = 1; i <= n; i++)
{
matrix[i] |= col(i);
}
int last = 0;
for(int i = 1; i <= n; i++)
{
int pos = last+1;
while(pos <= n &&( (matrix[pos]>>i) & 1)==0)
pos++;
if(pos > n) continue;
last = pos;
swap(matrix[pos], matrix[last]);
for(int j = 1; j <= n; j++)
{
//一定要排除第pos行
if(j==last) continue;
if((matrix[j] & col(i)))
{
matrix[j] ^= matrix[last];
}
}
}
for(int i = 1; i <= n; i++)
{
if((matrix[i] >> 1) == 0 && matrix[i] != 0)
{
logical = false;
break;
}
if(!matrix[i]) cnt++;
}
if(logical)
{
if(cnt==0) printf("1\n");
else{
int ans = 1 << cnt;
printf("%d\n", ans);
}
}
else
{
printf("Oh,it's impossible~!!\n");
}
}
return 0;
}
总结
答案控制:不需要设置太多的标志,一个ans就够了
if(logical)
{
if(cnt==0) printf("1\n");
else{
int ans = 1 << cnt;
printf("%d\n", ans);
}
}
else
{
printf("Oh,it's impossible~!!\n");
}
- 默认ans = 1;
- 如果不可行,就让ans = 0;
- 如果有一个自由元,那么就让ans << 1;
欣赏
int last = 0;//需要给一个last
//以防万一
/*
1 0 0 1
0 0 1 0
0 0 1 0
这种情况
*/
for(int i = 1; i <= n; i++)//如果行数和列数不相等,还是选取较大的为好
{
int pos = last+1;
while(pos <= n &&( (matrix[pos]>>i) & 1)==0)
pos++;
if(pos > n) continue;
last = pos;
swap(matrix[pos], matrix[last]);
for(int j = 1; j <= n; j++)
{
//一定要排除第last行
if(j==last) continue;
if((matrix[j] & col(i)))
{
matrix[j] ^= matrix[last];
}
}
}
线性空间
定义
线性空间是一个向量集合,并且关于一下两个运算封闭:
- 向量加法
- 向量数乘
如果一个向量可以被若干个向量通过向量乘法以及向量加法表示,那么就称这个向量可以被这几个向量线性标出。
线性空间的产生方法:\(a_1,a_2.....a_k\)所能表示的所有向量所构成的集合。
\(a_1,a_2.....a_k\)被称作生成子集。
线性相关&线性无关
任意在向量空间中选出若干个向量,如果某个向量可以被其他向量所表示,那么这些向量线性相关。否则线性无关。
线性空间的基底(基)
- 定义一:线性无关的生成子集;
- 定义二:极大线性无关子集。
线性空间的维数:一个线性空间的所有基包含的向量个数相等,成为维数。
对于一个m行n列的矩阵,如果把每一行看做是长度为m向量(行向量)。这n个向量所能表示的所有向量组成一个线性空间,线性空间的维数就称为矩阵的行秩。同理有列秩。
易知矩阵的行秩等于列秩。统称为秩。
在对一个矩阵进行化简之后,所有的非零行向量就是一个基(初等行变换不改变这些行向量所能表示的线性空间)个数就是矩阵的秩。
ACWing209. 装备购买
对于所有装备,把一个装备看成是m维的向量。总体看成是n*m矩阵,然后求出矩阵的基底就行(但是要注意有一个贪心,在每一次选择系数非零的矩阵的时候,应该选择对应的价钱是最小的)
代码
在参考标准代码之后所写的代码:
#include <bits/stdc++.h>
using namespace std;
//注意矩阵运算需要使用double型
#define N 510
long double matrix[N][N];
long double price[N];
const long double eps = 1e-8;
int main()
{
long double ans = 0;
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%Lf", &matrix[i][j]);
for(int i = 1; i <= n; i++)
scanf("%Lf", &price[i]);
int dim = 0;//dim表示基底的数量(和我之前的last相似)
for(int i = 1; i <= m; i++)
{
int now = 0;
for(int j = dim+1; j<= n; j++)
{
if(fabs(matrix[j][i]) > eps && (now==0 || price[j] < price[now]))
now = j;
}
if(now==0)//说明这一个元素是自由元。
continue;
dim++;
ans += price[now];
for(int j = 1; j <= m; j++)
swap(matrix[dim][j], matrix[now][j]);
swap(price[now], price[dim]);
for(int j = 1; j <= n; j++)
if(fabs(matrix[j][i]) > eps && j != dim)
{
long double rate = matrix[j][i] / matrix[dim][i];
for(int k = 1; k <= m; k++)
{
matrix[j][k] -= matrix[dim][k] * rate;
}
}
}
printf("%d %.0Lf", dim, ans);
return 0;
}
总结:
这道题目失误的地方有两个:
一个是浮点类型的数字在和其他数字进行比较大小的时候,我没有加fabs,导致错误。
还有一点就这道题目卡了double
是在以后所有的数据中,我尽量采用long double
来进行运算。
long double的注意事项:
- 输入应该采用
%Lf
- 输出应该使用
%Lf
- 对应的函数使用
fabsl(),cosl()
等等。
#include <bits/stdc++.h>
using namespace std;
//注意矩阵运算需要使用double型
#define N 510
long double matrix[N][N];
long double price[N];
const long double eps = 1e-8;
int main()
{
long double ans = 0;
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
scanf("%Lf", &matrix[i][j]);
for(int i = 1; i <= n; i++)
scanf("%Lf", &price[i]);
int dim = 0;//dim表示基底的数量(和我之前的last相似)
for(int i = 1; i <= m; i++)
{
int now = 0;
for(int j = dim+1; j<= n; j++)
{
if(fabs(matrix[j][i]) > eps && (now==0 || price[j] < price[now]))
now = j;
}
if(now==0)//说明这一个元素是自由元。
continue;
dim++;
ans += price[now];
for(int j = 1; j <= m; j++)
swap(matrix[dim][j], matrix[now][j]);
swap(price[now], price[dim]);
for(int j = 1; j <= n; j++)
if(fabs(matrix[j][i]) > eps && j != dim)
{
long double rate = matrix[j][i] / matrix[dim][i];
for(int k = 1; k <= m; k++)
{
matrix[j][k] -= matrix[dim][k] * rate;
}
}
}
printf("%d %.0Lf", dim, ans);
return 0;
}
AcWing210. 异或运算
思路:注意线性空间的推广!
异或与线性空间具有一致性
你可以从中选取一些(至少一个)进行异或(xor)运算,从而得到很多不同的结果。
这句话提示了讨论的范围是在把数字当做向量以后所得到的异或空间。
可以通过消元来把复杂的问题变得清晰易懂。
DEBUG总结
- 对于位运算,判断某一位是不是1的办法
(x>>i)&1
- 这道题目我万万没有想到:
竟然如果矩阵的秩等于总的行数,那么对于所给的这n个数字,无论如何也整不出一个0来。
但是如果dim小于总的行数,那么这n个向量是线性无关的。所以可以取大于等于1个数字,把他们给消去。
#include <bits/stdc++.h>
using namespace std;
#define N 10010
long long matrix[N];
int main()
{
int sddsdsafdasg = 1;
int T;
cin >> T;
while(T--)
{
printf("Case #%d:\n", sddsdsafdasg++);
int dim = 0;
int n, m;
cin >> n;
for(int i = 1; i <= n; i++) scanf("%lld", &matrix[i]);
/*进行高斯消元*/
for(int i = 63; i >= 0; i--)
{
int now = 0;
for(int j = dim+1; j <= n; j++)
if(((matrix[j] >> i) & 1) != 0)
{
now = j;
break;
}
if(now == 0) continue;
dim++;
swap(matrix[dim], matrix[now]);
for(int j = 1; j<= n; j++)
{
if( ((matrix[j]>>i) & 1) == 1 && j!=dim)
matrix[j] ^= matrix[dim];
}
}
/*高斯消元完成*/
scanf("%d", &m);
for(int t = 1; t <= m; t++)
{
int base = 1;
if(dim >= n) base = 0;
int cnt = dim;
long long ans = 0;
long long q;
scanf("%lld", &q);
q-=base;
if((unsigned long long)q >= (1LL << dim))
{
printf("-1\n");
continue;
}
while(q)
{
if(q&1)
ans ^= matrix[cnt];
q >>= 1;
cnt--;
}
printf("%lld\n", ans);
}
}
return 0;
}
算法竞赛进阶指南0x35高斯消元与线性空间的更多相关文章
- 0x35 高斯消元与线性空间
颓了十天回来做题果然…… 感觉还是很有收获的,这两以前都没学过 bzoj1013: [JSOI2008]球形空间产生器sphere poj1830(upd) 之前做得很烂还被 D飞*2 了..重做一次 ...
- 《算法竞赛进阶指南》0x10 基本数据结构 Hash
Hash的基本知识 字符串hash算法将字符串看成p进制数字,再将结果mod q例如:abcabcdefg 将字母转换位数字(1231234567)=(1*p9+2*p8+3*p7+1*p6+2*p5 ...
- 《算法竞赛进阶指南》1.4Hash
137. 雪花雪花雪花 有N片雪花,每片雪花由六个角组成,每个角都有长度. 第i片雪花六个角的长度从某个角开始顺时针依次记为ai,1,ai,2,-,ai,6. 因为雪花的形状是封闭的环形,所以从任何一 ...
- bzoj 1787 && bzoj 1832: [Ahoi2008]Meet 紧急集合(倍增LCA)算法竞赛进阶指南
题目描述 原题连接 Y岛风景美丽宜人,气候温和,物产丰富. Y岛上有N个城市(编号\(1,2,-,N\)),有\(N-1\)条城市间的道路连接着它们. 每一条道路都连接某两个城市. 幸运的是,小可可通 ...
- POJ1639 算法竞赛进阶指南 野餐规划
题目描述 原题链接 一群小丑演员,以其出色的柔术表演,可以无限量的钻进同一辆汽车中,而闻名世界. 现在他们想要去公园玩耍,但是他们的经费非常紧缺. 他们将乘车前往公园,为了减少花费,他们决定选择一种合 ...
- 算法竞赛进阶指南 0x00 基本算法
放在原来这个地方不太方便,影响阅读体验.为了读者能更好的刷题,另起一篇随笔. 0x00 基本算法 0x01 位运算 [题目][64位整数乘法] 知识点:快速幂思想的灵活运用 [题目][最短Hamilt ...
- 算法竞赛进阶指南--快速幂,求a^b mod p
// 快速幂,求a^b mod p int power(int a, int b, int p) { int ans = 1; for (; b; b >>= 1) { if (b &am ...
- 算法竞赛进阶指南 0x50 总论
目录 AcWing895. 最长上升子序列 方法一 方法二 当询问最长子序列是哪些的时候 896. 最长上升子序列 II 思路 O(NlogN)做法:贪心+二分 代码 AcWing\897. 最长公共 ...
- 算法竞赛进阶指南0x41并查集
并查集简介 并查集的两类操作: Get 查询任意一个元素是属于哪一个集合. Merge 把两个集合合并在一起. 基本思想:找到代表元. 注意有两种方法: 使用一个固定的值(查询方便,但是在合并的时候需 ...
随机推荐
- 一文搞懂CDN加速原理
开源Linux 长按二维码加关注~ 一.什么是 CDN CDN的全称是(Content Delivery Network),即内容分发网络.其目的是通过在现有的Internet中增加一层新的CACHE ...
- Nginx的mirror指令能干啥?
mirror 流量复制 Nginx的 mirror 指令来自于 ngx_http_mirror_module 模块 Nginx Version > 1.13.4 mirror 指令提供的核心功能 ...
- 用 Python 远程控制 Windows 服务器,太好用了!
在很多企业会使用闲置的 Windows 机器作为临时服务器,有时候我们想远程调用里面的程序或查看日志文件 Windows 内置的服务「 winrm 」可以满足我们的需求 它是一种基于标准简单对象访问协 ...
- 基于SqlSugar的开发框架的循序渐进介绍(1)--框架基础类的设计和使用
在实际项目开发中,我们可能会碰到各种各样的项目环境,有些项目需要一个大而全的整体框架来支撑开发,有些中小项目这需要一些简单便捷的系统框架灵活开发.目前大型一点的框架,可以采用ABP或者ABP VNex ...
- MinIO学习
1.Minio及背景 Minio是一个开源的分布式文件存储系统,它基于 Golang 编写,虽然轻量,却拥有着不错的高性能,可以将图片.视频.音乐.pdf这些文件存储到多个主机,可以存储到多个Linu ...
- vscode编写的程序中文乱码怎么办?
(以下教程在源码文件的编码是utf-8的基础上进行!) (dev的源码文件是GBK编码,或者是GB2312?我现在好久没用dev,关于dev的信息可能有错误. 如果拿dev编写的代码用vscode打开 ...
- 【工具-Nginx】从入门安装到高可用集群搭建
文章已收录至https://lichong.work,转载请注明原文链接. ps:欢迎关注公众号"Fun肆编程"或添加我的私人微信交流经验 一.Nginx安装配置及常用命令 1.环 ...
- 以圆类 Circle 为基础设计球类 Sphere
学习内容:实验二以圆类 Circle 为基础设计球类 Sphere 代码示例: import java.util.Scanner; class Point{ private double x; pri ...
- [算法学习] 换根dp
换根dp 一般来说,我们做题的树都是默认 \(1\) 为根的.但是有些题目需要计算以每个节点为根时的内容. 朴素的暴力:以每个点 \(u\) 作为 \(root\) 暴力dfs下去,复杂度\(O(n^ ...
- SQL中的数字、字母和汉字
知识点001 当变量的数据类型为VARCHAR时,变量赋值后,变量中的字符所占字节数,数字和字母是1个bytes,汉字是2个bytes; 当变量的数据类型为NVARCHAR时,变量赋值后,变量中的字符 ...