目录

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

线性空间

1.算法分析

1.1 高斯消元

模拟线性代数的运算

1.2 线性基

    线性基是向量空间的一组基,通常可以解决有关异或的一些题目。
    通俗一点的讲法就是由一个集合构造出来的另一个集合,它有以下几个性质:

  1. 线性基的元素能相互异或得到原集合的元素的所有相互异或得到的值。
  2. 线性基是满足性质 1 的最小的集合。
  3. 线性基没有异或和为 0 的子集。
  4. 线性基中每个元素的异或方案唯一,也就是说,线性基中不同的异或组合异或出的数都是不一样的。
  5. 线性基中每个元素的二进制最高位互不相同

2.模板

2.1 高斯消元

2.1.1 解累加方程

\begin{cases}
A11X1+A12X2+...+A1nXn=B1 \\
A21X1+A22X2+...+A2nXn=B2 \\
...........................................................\\
Am1X1+Am2X2+...+AmnXn=Bm
\end{cases}
2.1.1.1 整数系数
#include <bits/stdc++.h>

using namespace std;

const int N = 50;

int a[N][N];//增广矩阵
int x[N];//解集
bool free_x[N];//标记是否是不确定的变元

int gcd(int  a, int b)
{
    return b ? gcd(b, a % b): a;
}

inline int lcm(int a,int b){
    return a / gcd(a,b) * b;//先除后乘防溢出
}

// 高斯消元法解方程组(Gauss-Jordan elimination).(-2表示有浮点数解,但无整数解,
//-1表示无解,0表示唯一解,大于0表示无穷解,并返回自由变元的个数)
//有equ个方程,var个变元。增广矩阵行数为equ,分别为0到equ-1,列数为var+1,分别为0到var.
int gauss(int equ, int var){

    // 初始化
    for(int i = 0; i <= var; i++){
        x[i] = 0;
        free_x[i] = true;
    }

    //转换为阶梯阵.
    int col = 0, k = 0;
    for(k = 0; k < equ && col < var; k++, col++){// 枚举当前处理的行.
    // 找到该col列元素绝对值最大的那行与第k行交换.(为了在除法时减小误差)
        int max_r = k;
        for(int i = k + 1; i < equ;i++){
            if(abs(a[i][col]) > abs(a[max_r][col])) max_r = i;
        }
        if(max_r != k){// 与第k行交换.
            for(int j = k; j < var + 1; j++) swap(a[k][j], a[max_r][j]);
        }
        if(a[k][col] == 0){// 说明该col列第k行以下全是0了,则处理当前行的下一列.
            k--;
            continue;
        }
        for(int i = k + 1; i < equ; i++){// 枚举要删去的行.
            if(a[i][col] != 0){
                int LCM = lcm(abs(a[i][col]), abs(a[k][col]));
                int ta = LCM / abs(a[i][col]);
                int tb = LCM / abs(a[k][col]);
                if(a[i][col] * a[k][col] < 0) tb = -tb;//异号的情况是相加
                for(int j = col; j < var + 1; j++) a[i][j] = a[i][j] * ta - a[k][j] * tb;
            }
        }
    }
    
    // 1. 无解的情况: 化简的增广阵中存在(0, 0, ..., a)这样的行(a != 0).
    for (int i = k; i < equ; i++){ // 对于无穷解来说,如果要判断哪些是自由变元,那么初等行变换中的交换就会影响,则要记录交换.
        if (a[i][col] != 0) return -1;
    }
    // 2. 无穷解的情况: 在var * (var + 1)的增广阵中出现(0, 0, ..., 0)这样的行,即说明没有形成严格的上三角阵.
    // 且出现的行数即为自由变元的个数.
    if (k < var){
        return var - k; // 自由变元有var - k个.
    }
    // 3. 唯一解的情况: 在var * (var + 1)的增广阵中形成严格的上三角阵.
    // 计算出Xn-1, Xn-2 ... X0.
    for (int i = var - 1; i >= 0; i--){
        int temp = a[i][var];
        for (int j = i + 1; j < var; j++){
            if (a[i][j] != 0) temp -= a[i][j] * x[j];
        }
        if (temp % a[i][i] != 0) return -2; // 说明有浮点数解,但无整数解.
        x[i] = temp / a[i][i];
    }
    return 0;
}

int main(void){
    int equ,var;
    while (scanf("%d %d", &equ, &var) != EOF){
        memset(a, 0, sizeof(a));
        for (int i = 0; i < equ; i++){
            for (int j = 0; j < var + 1; j++){
                scanf("%d", &a[i][j]);
            }
        }
        int free_num = gauss(equ,var);
        if (free_num == -1) printf("无解!\n");
        else if (free_num == -2) printf("有浮点数解,无整数解!\n");
        else if (free_num > 0){
            printf("无穷多解! 自由变元个数为%d\n", free_num);
            for (int i = 0; i < var; i++){
                if (free_x[i]) printf("%d 是不确定的\n", i + 1);
                else printf("%d: %d\n", i + 1, x[i]);
            }
        }else{
            for (int i = 0; i < var; i++){
                printf("%d: %d\n", i + 1, x[i]);
            }
        }
        printf("\n");
    }
    return 0;
}
2.1.1.2 浮点数系数
#include <bits/stdc++.h>

using namespace std;

const int N = 110;
const double eps = 1e-6;

int n, m;
double a[N][N];  // 存放参数

// 求解线性方程组,m个方程,n个自变量
int gauss()
{
    int c, r;
    for (c = 0, r = 0; c < n && r < m; c ++, r++)  // 正在找第i元
    {
        // 找出第一个元素最大的那一行
        int t = r; 
        for (int i = r; i < m; i ++ )
            if (fabs(a[i][c]) > fabs(a[t][c]))
                t = i;

        if (fabs(a[t][c]) < eps) {
            r--;
            continue;  // 如果是0,那么不用管
        }

        for (int i = c; i < n + 1; i ++ ) swap(a[t][i], a[r][i]);  // 把第一个元素最大的那一行放到第一行
        for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];  // 把第一行的首元消成1

        for (int i = r + 1; i < m; i ++ )  // 把后面每行都和刚刚首元消成1的那行做运算
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j -- )
                    a[i][j] -= a[r][j] * a[i][c];
    }

    for (int i = r; i < m; i++)
        if (fabs(a[i][n]) > eps) return 2; // 0=非0,无解
        
    if (r < n){
        return 1; // 无穷多解
    }

    // 唯一解,把解放到n+1列
    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] -= a[j][n] * a[i][j];

    return 0;
}

int main()
{
    // 最后一列存放b,前n列存放a
    cin >> m >> n;
    for (int i = 0; i < m; i ++ )
        for (int j = 0; j < n + 1; j ++ )
            cin >> a[i][j];

    int t = gauss();

    // 判断答案是否有解
    if (t == 0)  // 有解
    {
        for (int i = 0; i < n; i ++ ) printf("%.2lf\n", a[i][n]);  // 最后的第n+1列存放答案
    }
    else if (t == 1) puts("Infinite group solutions");  // 无穷多解
    else puts("No solution");  // 无解

    return 0;
}

2.1.2 解异或方程

\begin{cases}
A11X1 xor A12X2 xor ... xor A1nXn=B1 \\
A21X1 xor A22X2 xor ... xor A2nXn=B2 \\
.........................\\
Am1X1 xor Am2X2 xor ... xor AmnXn=Bm
\end{cases}
#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int n, m;
int a[N][N];  // 存放参数

// 高斯消元
int gauss()
{
    int c, r;
    for (c = 0, r = 0; c < n && r < m; c ++, r++)
    {
        // 找首元绝对值最大的那行
        int t = r;
        for (int i = r; i < m; i ++ )
            if (a[i][c])
                t = i;

        if (!a[t][c]) {
            r--;
            continue;
        }

        // 把首元绝对值最大的那行放到第一行
        for (int i = c; i <= n; i ++ ) swap(a[r][i], a[t][i]);
        for (int i = r + 1; i < m; i ++ )
            if (a[i][c])
                for (int j = n; j >= c; j -- )
                    a[i][j] ^= a[r][j];  // 把其他行都和首元绝对值最大的那行做异或
    }

    for (int i = r; i < m; i++)
        if (a[i][n]) return 2; // 0=非0,无解
        
    if (r < n){
        return 1; // 无穷多解
    }

    // 唯一解
    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] ^= a[i][j] * a[j][n];


    return 0;
}

int main()
{
    cin >> m >> n;
    // 读入参数, 前n列为a,最后一列为b
    for (int i = 0; i < m; i ++ )
        for (int j = 0; j < n + 1; j ++ )
            cin >> a[i][j];

    int t = gauss();

    // 输出答案
    if (t == 0)  // 唯一解
    {
        for (int i = 0; i < n; i ++ ) cout << a[i][n] << endl;
    }
    else if (t == 1) puts("Multiple sets of solutions");  // 多解
    else puts("No solution");  // 无解

    return 0;
}

2.2 线性基

#include<bits/stdc++.h>
 
using namespace std;
 
typedef long long LL;
 
struct Linear_Basis{
 
    LL d[61],p[61];
    int cnt, flag;
 
    Linear_Basis() {
        memset(d, 0 ,sizeof(d));
        memset(p, 0, sizeof(p));
        cnt = 0;  // 当前线性基内元素个数
        flag = 0;  // 不存在0
    }
 
    // 构造线性基:逐个元素插入
    bool insert(LL val) {
        for (int i = 60;i >= 0; i--) {
            if (val & (1ll << i)) {  // 判断当前i位能否插入
                if (!d[i]) {  // 如果当前i位没插入过元素
                    d[i] = val;  // 插入
                    return true;  // 插入成功
                }
                val ^= d[i];
            }
        }
        flag = 1;  // 存在0
        return false; // 插入失败
    }
 
    // 查询第最大异或和
    LL query_max() {
        LL ret = 0;
        for (int i = 60; i >= 0; i--)
            if ((ret ^ d[i]) > ret)
                ret ^= d[i];
        return ret;
    }
 
    // 查询最小异或和
    LL query_min() {
        for (int i = 0;i <= 60; i++)
            if (d[i])
                return d[i];
        return 0;
    }
 
    // 重构,使之形成对角矩阵
    void rebuild() {
        for (int i = 60;i >= 0; i--)  // 当前i位时,把后面的j位情况都和i位情况做异或
            for (int j = i - 1;j >= 0; j--)
                if (d[i] & (1ll << j))
                    d[i] ^= d[j];
        for (int i = 0; i <= 60; i++)  // 记录重构结果
            if (d[i])
                p[cnt++] = d[i];
    }
 
    // 查询第k小
    LL kthquery(LL k){
        LL ret = 0;
        if (flag) {  // 存在0
            k--;
            if (!k) return (LL)0;
        }
        if (k >= (1ll << cnt))  // 只能由2^cnt - 1个
            return -1;
        for (int i = 60; i >= 0; i--)
            if (k & (1LL << i))  // 每位的贡献为2^i
                ret ^= p[i];
        return ret;
    }
 
};
 
// 合并线性基
Linear_Basis merge(const Linear_Basis &n1, const Linear_Basis &n2) {
    Linear_Basis ret = n1;
    for (int i = 60; i >= 0; i--)
        if (n2.d[i])
            ret.insert(n2.d[i]);
    return ret;
}
 
int main(){
 
    int n;
    scanf("%d",&n);
    Linear_Basis lb;
    for(int i=0;i<n;i++){
        LL a;
        scanf("%lld",&a);
        lb.insert(a);
    }
    printf("%lld\n",lb.query_max());
    return 0;
}

3.例题

3.1 高斯消元

acwing207球形空间产生器
已经知道n维球体的球面上n+1个点的坐标,算出球心坐标
n~11
 线性空间 算法

#include <bits/stdc++.h>

using namespace std;

const int N = 11;
const double eps = 1e-6;

int n, m;
double a[N][N];  // 存放参数
double tmp[N];

// 求解线性方程组
int gauss()
{
    int c, r;
    for (c = 0, r = 0; c < n && r < m; c ++, r++)  // 正在找第i元
    {
        // 找出第一个元素最大的那一行
        int t = r; 
        for (int i = r; i < m; i ++ )
            if (fabs(a[i][c]) > fabs(a[t][c]))
                t = i;

        if (fabs(a[t][c]) < eps) {
            r--;
            continue;  // 如果是0,那么不用管
        }

        for (int i = c; i < n + 1; i ++ ) swap(a[t][i], a[r][i]);  // 把第一个元素最大的那一行放到第一行
        for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c];  // 把第一行的首元消成1

        for (int i = r + 1; i < m; i ++ )  // 把后面每行都和刚刚首元消成1的那行做运算
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j -- )
                    a[i][j] -= a[r][j] * a[i][c];
    }

    for (int i = r; i < m; i++)
        if (fabs(a[i][n]) > eps) return 2; // 0=非0,无解
        
    if (r < n){
        return 1; // 无穷多解
    }

    // 唯一解,把解放到n+1列
    for (int i = n - 1; i >= 0; i -- )
        for (int j = i + 1; j < n; j ++ )
            a[i][n] -= a[j][n] * a[i][j];

    return 0;
}

int main()
{
    // 最后一列存放b,前n列存放a
    cin >> n;
    m = n;
    for (int i = 0; i < n; ++i) cin >> tmp[i];
    
    for (int i = 0; i < n; ++i) {
        double t, b = 0;
        for (int j = 0; j < n; ++j) {
            cin >> t;
            a[i][j] = 2 * (t - tmp[j]);
            b += t * t - tmp[j] * tmp[j];
        }
        a[i][n] = b;
    }

    gauss();

    for (int i = 0; i < n; i ++ ) printf("%.3lf ", a[i][n]); 

    return 0;
}

acwing208开关问题
有N个相同的开关,每个开关都与某些开关有着联系,每当你打开或者关闭某个开关的时候,其他的与此开关相关联的开关也会相应地发生变化,即这些相联系的开关的状态如果原来为开就变为关,如果为关就变为开。对于任意一个开关,最多只能进行一次开关操作。计算有多少种可以达到指定状态的方法。(不计开关操作的顺序)
N~29

/*
每个灯最后的变化情况是由许多个开关共同决定的,可以列异或方程组求解
第i个方程表示第i个灯是由哪些开关决定的
比如:
1号开关按下能够使得1、2、3变化
2号开关按下能够使得2、3变化
3号开关按下能够使得3变化
那么列的方程组为:
x1 = st1
x1 ^ x2 = st2
x1 ^ x2 ^ x3 = st3
其中的xi表示第i个开关的按下状态,按下为1,没按下为0
等号后面的st1、st2、st3表示灯泡的变化情况,变化为1,没变化为0
*/
#include <bits/stdc++.h>

using namespace std;

const int N = 110;

int n, t, m;
int a[N][N];  // 存放参数

int qmi(int a, int k) {
    int res = 1;
    while (k) {
        if (k & 1) res = res * a;
        k >>= 1;
        a = a * a;
    }
    return res;
}

void gauss()
{
    int c, r;
    for (c = 0, r = 0; c < n && r < m; c ++, r++)
    {
        // 找首元绝对值最大的那行
        int t = r;
        for (int i = r; i < m; i ++ )
            if (a[i][c])
                t = i;

        if (!a[t][c]) {
            r--;
            continue;
        }

        // 把首元绝对值最大的那行放到第一行
        for (int i = c; i <= n; i ++ ) swap(a[r][i], a[t][i]);
        for (int i = r + 1; i < m; i ++ )
            if (a[i][c])
                for (int j = n; j >= c; j -- )
                    a[i][j] ^= a[r][j];  // 把其他行都和首元绝对值最大的那行做异或
    }

    for (int i = r; i < m; i++)
        if (a[i][n]) {
            cout << "Oh,it's impossible~!!\n";  // 无解
            return;
        }

    cout << qmi(2, n - r) << endl;  // 最后看一下自由元的个数,答案为qmi(2, 自由元的个数)
    return ;
}

int main()
{
    cin >> t;
    while (t--) {
        memset(a, 0, sizeof a);
        cin >> n;
        m = n;
        for (int i = 0; i < n; ++i) cin >> a[i][n];
        for (int i = 0, t; i < n; ++i) {
            cin >> t;
            a[i][n] ^= t;  // 记录最后每个灯的变化状态,变化为1,没变化为0
            a[i][i] = 1;  // 每个开关都一定会改变自己
        }
        
        int t1, t2;
        while (cin >> t1 >> t2 && t1 && t2) a[t2 - 1][t1 - 1] = 1; // 记录联动关系
    
        gauss();  // 高斯消元
    }

    return 0;
}

acwing227小部件厂
简单小部件仅需要3天,但最复杂的小部件可能需要多达9天。这里有记录记载了每个工人开始制作的日期,完成制作的日期以及制作的小部件型号.但是问题是记录没有明确记载工人开始和完成工作的确切日期,只记录了该天是星期几。尽管如此,这些信息也是有些帮助的:例如,如果一个人在星期二开始制作一个41型小部件,并在周五完成,那么我们就知道了制作一个41型小部件需要4天时间(因为最多不超过9天,所以不可能是11天或更多)。您的任务是从这些记录中(如果可能)找出制作不同类型的小部件所需的天数。
1≤n,m≤300,1≤k≤10000

#include <bits/stdc++.h>

using namespace std;

const int N = 310;
const double eps = 1e-6;

unordered_map<string, int> date;
int n, m;
int a[N][N];  // 存放参数
int cnt[N];
int x[N];//解集
bool free_x[N];//标记是否是不确定的变元

int gcd(int  a, int b)
{
    return b ? gcd(b, a % b): a;
}

inline int lcm(int a,int b){
    return a / gcd(a,b) * b;//先除后乘防溢出
}

// 扩展欧几里得算法:ax+by=gcd(a, b),返回值为d=gcd(a, b)
int exgcd(int a, int b, int &x, int &y)
{
    if (!b)  // b==0时,x=1, y=0
    {
        x = 1, y = 0;
        return a;
    }
    int d = exgcd(b, a % b, y, x); // b != 0时,gcd(a, b) = gcd(b, a % b), x = x, y = y - a/b * x;
    y -= a / b * x;
    return d;
}

int getInv(int a,int mod)//求a在mod下的逆元,不存在逆元返回-1
{
    int x, y;
    int d = exgcd(a, mod, x, y);
    return d == 1 ? (x % mod + mod) % mod: -1;
}

// 高斯消元法解方程组(Gauss-Jordan elimination).(-2表示有浮点数解,但无整数解,
//-1表示无解,0表示唯一解,大于0表示无穷解,并返回自由变元的个数)
//有equ个方程,var个变元。增广矩阵行数为equ,分别为0到equ-1,列数为var+1,分别为0到var.
int gauss(int equ, int var){

    // 初始化
    for(int i = 0; i <= var; i++){
        x[i] = 0;
        free_x[i] = true;
    }

    //转换为阶梯阵.
    int col = 0, k = 0;
    for(k = 0; k < equ && col < var; k++, col++){// 枚举当前处理的行.
    // 找到该col列元素绝对值最大的那行与第k行交换.(为了在除法时减小误差)
        int max_r = k;
        for(int i = k + 1; i < equ;i++){
            if(abs(a[i][col]) > abs(a[max_r][col])) max_r = i;
        }
        if(max_r != k){// 与第k行交换.
            for(int j = k; j < var + 1; j++) swap(a[k][j], a[max_r][j]);
        }
        if(a[k][col] == 0){// 说明该col列第k行以下全是0了,则处理当前行的下一列.
            k--;
            continue;
        }
        for(int i = k + 1; i < equ; i++){// 枚举要删去的行.
            if(a[i][col] != 0){
                int LCM = lcm(abs(a[i][col]), abs(a[k][col]));
                int ta = LCM / abs(a[i][col]);
                int tb = LCM / abs(a[k][col]);
                if(a[i][col] * a[k][col] < 0) tb = -tb;//异号的情况是相加
                for(int j = col; j < var + 1; j++) a[i][j] = ((a[i][j] * ta - a[k][j] * tb) % 7 + 7 ) % 7;
            }
        }
    }
    
    // 1. 无解的情况: 化简的增广阵中存在(0, 0, ..., a)这样的行(a != 0).
    for (int i = k; i < equ; i++){ // 对于无穷解来说,如果要判断哪些是自由变元,那么初等行变换中的交换就会影响,则要记录交换.
        if (a[i][col] != 0) return -1;
    }
    // 2. 无穷解的情况: 在var * (var + 1)的增广阵中出现(0, 0, ..., 0)这样的行,即说明没有形成严格的上三角阵.
    // 且出现的行数即为自由变元的个数.
    if (k < var){
        return var - k; // 自由变元有var - k个.
    }
    // 3. 唯一解的情况: 在var * (var + 1)的增广阵中形成严格的上三角阵.
    // 计算出Xn-1, Xn-2 ... X0.
    for (int i = var - 1; i >= 0; i--){
        int temp = a[i][var];
        for (int j = i + 1; j < var; j++){
            if (a[i][j] != 0) temp =  ((temp - a[i][j] * x[j]) % 7 + 7) % 7;
        }
        x[i] = temp * getInv(a[i][i], 7);  // 取模下不能直接除法,要乘逆元
        x[i] = ((x[i] % 7 + 7) % 7);
    }
    return 0;
}

int main()
{
    date["MON"] = 1, date["TUE"] = 2, date["WED"] = 3, date["THU"] = 4;
    date["FRI"] = 5, date["SAT"] = 6, date["SUN"] = 7;
    while (scanf("%d%d", &n, &m) != EOF && n && m) {
        
        // 构造方程
        memset(a, 0 ,sizeof(a));
        for(int i = 0, k; i < m; ++i)
        {
            string t1, t2;
            cin >> k >> t1 >> t2;
            a[i][n] = ((date[t2] - date[t1] + 1) % 7 + 7 ) % 7;
            for(int j = 1, tt; j <= k; ++j)
            {
                scanf("%d", &tt);
                a[i][tt - 1] = (a[i][tt - 1] + 1) % 7;
            }
        }
        
        // 输出答案
        int free_num = gauss(m,n);
        if (free_num == -1) printf("Inconsistent data.\n");
        else if (free_num > 0) printf("Multiple solutions.\n");
        else{
            for (int i = 0; i < n; i++){
                if (x[i] < 3) x[i] += 7;
                printf("%d ", x[i]);
            }
            cout << endl;
        }  
    }

    return 0;
}

3.2 线性基

P4570 [BJWC2011]元素

给n个整数,每个整数带一个权值v,求一个权值和最大的线性基
按权值v从大->小排序,依次插入线性基

#include<bits/stdc++.h>
 
using namespace std;
 
typedef long long LL;

int const N = 1e3 + 10;
struct MAGIC {
    LL val, magic;
    bool operator<(const MAGIC &t) const {
        return magic > t.magic;
    }
}magic[N];
 
struct Linear_Basis{
 
    LL d[61],p[61];
    int cnt, flag = 0;
 
    Linear_Basis(){
        memset(d, 0, sizeof(d));
        memset(p, 0, sizeof(p));
        cnt = 0;
        flag = 0;
    }
 
    // 构造线性基:逐个元素插入
    bool ins(LL val) {
        for (int i = 60;i >= 0; i--) {
            if (val & (1ll << i)) {  // 判断当前i位能否插入
                if (!d[i]) {  // 如果当前i位没插入过元素
                    d[i] = val;  // 插入
                    break;
                }
                val ^= d[i];
            }
        }
        return val > 0; // 返回1说明插入成功;返回0插入失败,说明出现异或和为0的情况
    }
};
 
int main(){
 
    int n;
    scanf("%d",&n);
    Linear_Basis lb;
    LL sum = 0;
    for(int i=0;i<n;i++){
        LL t1, ma;
        cin >> t1 >> ma;
        magic[i].val = t1, magic[i].magic = ma;
    }
    sort(magic, magic + n);
    for (int i = 0; i < n; ++i) {
        if (lb.ins(magic[i].val)) sum += magic[i].magic;
    }
    printf("%lld\n", sum);
    return 0;
}

acwing229 新nim游戏
第1和2回合,可以拿走任意堆,但不能全拿,从第3回合后开始和普通nim游戏一样。问第1回合拿走多少堆才能必胜。如果能必胜,输出拿走的石子数目;如果不能,输出-1.

/* 要使必胜,那么第1回合拿完后,其余剩余石子无论怎么异或都不会出现0,那么把石子从大到小排序,逐个往线性基内插入。如果插入不了,说明出现0,必须拿走。 */
#include<bits/stdc++.h>
 
using namespace std;
 
typedef long long LL;

int const N = 1e3 + 10;
LL a[N];
 
struct Linear_Basis{
 
    LL d[61],p[61];
    int cnt, flag;
 
    Linear_Basis(){
        memset(d, 0, sizeof(d));
        memset(p, 0, sizeof(p));
        cnt = 0;
        flag = 0;
    }
 
    // 构造线性基:逐个元素插入
    bool ins(LL val) {
        for (int i = 60;i >= 0; i--) {
            if (val & (1ll << i)) {  // 判断当前i位能否插入
                if (!d[i]) {  // 如果当前i位没插入过元素
                    d[i] = val;  // 插入
                    break;
                }
                val ^= d[i];
            }
        }
        return val > 0; // 返回1说明插入成功;返回0插入失败,说明出现异或和为0的情况
    }
};
 
int main(){
 
    int n;
    scanf("%d",&n);
    Linear_Basis lb;
    LL sum = 0;
    for(int i=0;i<n;i++){
        cin >> a[i];
    }
    sort(a, a + n);
    reverse(a, a + n);
    for (int i = 0; i < n; ++i) {
        if (!lb.ins(a[i])) sum += a[i];
    }
    if (!sum) sum = -1;
    printf("%lld\n", sum);
    return 0;
}

acwing210异或运算
求第k小异或和

#include<bits/stdc++.h>
 
using namespace std;
 
typedef long long LL;
int const N = 1e4 + 10;
int t, n, m, kase;
LL a[N];
 
struct Linear_Basis{
 
    LL d[61],p[61];
    int cnt, flag;
 
    Linear_Basis() {
        memset(d, 0 ,sizeof(d));
        memset(p, 0, sizeof(p));
        cnt = 0;  // 当前线性基内元素个数
        flag = 0;  // 不存在0
    }
 
    // 构造线性基:逐个元素插入
    bool insert(LL val) {
        for (int i = 60;i >= 0; i--) {
            if (val & (1ll << i)) {  // 判断当前i位能否插入
                if (!d[i]) {  // 如果当前i位没插入过元素
                    d[i] = val;  // 插入
                    return true;  // 插入成功
                }
                val ^= d[i];
            }
        }
        flag = 1;  // 存在0
        return false; // 插入失败
    }
 
    // 重构,使之形成对角矩阵
    void rebuild() {
        for (int i = 60;i >= 0; i--)  // 当前i位时,把后面的j位情况都和i位情况做异或
            for (int j = i - 1;j >= 0; j--)
                if (d[i] & (1ll << j))
                    d[i] ^= d[j];
        for (int i = 0; i <= 60; i++)  // 记录重构结果
            if (d[i])
                p[cnt++] = d[i];
    }
 
    // 查询第k小
    LL kthquery(LL k){
        LL ret = 0;
        if (flag) {  // 存在0
            k--;
            if (!k) return (LL)0;
        }
        if (k >= (1ll << cnt))  // 只能由2^cnt - 1个
            return -1;
        for (int i = 60; i >= 0; i--)
            if (k & (1LL << i))  // 每位的贡献为2^i
                ret ^= p[i];
        return ret;
    }
 
};
 
int main(){
    cin >> t;
    while (t--) {
        printf("Case #%d:\n", ++kase);
        cin >> n;
        Linear_Basis lb;
        for (int i = 1; i <= n; ++i) {
            cin >> a[i];
            lb.insert(a[i]);
        }
        lb.rebuild();
        
        cin >> m;
        for (int i = 1; i <= m; ++i) {
            LL t;
            cin >> t;
            cout << lb.kthquery(t) << endl;
        }
    }
    return 0;
}

acwing228异或
    给一个无向连通图,求一条从1到n的路径(可以不是简单路径),使经过的边权的异或和最大。

/*
本题要求最大XOR路径,可以分析出来最大只要是个环,那么都可以经过
因此只需要找到一条1~n的路径,而后不断把环的权值异或,看是否能变大即可
分析一下找环,如果不是一个简单环,那么一定可以由简单环异或得到,因此只要找简单环即可
找简单环只要dfs判断环的思路即可
*/

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const N = 5e4 + 10, M = 1e5 + 10;
int e[M * 2], ne[M * 2], h[N], idx, st[N];
LL w[M * 2], a[N];
int n, m;

struct Linear_Basis{
 
    LL d[61],p[61];
    int cnt, flag;
 
    Linear_Basis() {
        memset(d, 0 ,sizeof(d));
        memset(p, 0, sizeof(p));
        cnt = 0;  // 当前线性基内元素个数
        flag = 0;  // 不存在0
    }
 
    // 构造线性基:逐个元素插入
    bool insert(LL val) {
        for (int i = 60;i >= 0; i--) {
            if (val & (1ll << i)) {  // 判断当前i位能否插入
                if (!d[i]) {  // 如果当前i位没插入过元素
                    d[i] = val;  // 插入
                    return true;  // 插入成功
                }
                val ^= d[i];
            }
        }
        flag = 1;  // 存在0
        return false; // 插入失败
    }
 
    // 查询第最大异或和
    LL query_max(LL x) {
        LL ret = x;
        for (int i = 60; i >= 0; i--)
            if ((ret ^ d[i]) > ret)
                ret ^= d[i];
        return ret;
    }
}lb;

void add(int a, int b, LL c) {
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
 
// 找简单环
void dfs(int u, int fa) {
    st[u] = 1;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue;
        
        
        if (st[j]) {
            lb.insert(a[u] ^ a[j] ^ w[i]);  // 找到环,插入线性基
        }
        else {  // 否则,更新
            a[j] = a[u] ^ w[i];  
            dfs (j, u);
        }
    }
}

int main() {
    cin >> n >> m;
    memset(h, -1, sizeof h);
    for (int i = 1, a, b; i <= m; ++i) {
        LL d;
        scanf("%d %d %lld", &a, &b, &d);
        add(a, b, d), add(b, a, d);
    }
    
    dfs (1, -1);
    cout << lb.query_max(a[n]) << endl;  // 找到1~n的路径,然后线性基找最大值
    return 0;
}

P3857 [TJOI2008]彩灯
一组彩灯是由一排 N 个独立的灯泡构成的,并且有 M 个开关控制它们。问有多少种样式可以展示?
N,M~50

// 灯泡开关就是0和1,选和不选,就是判断对角矩阵线性基选和不选
// 因此重构之后变成对角矩阵后,里面有x个基底,那么就要2^x种情况
// 这就等价于给定n个数,求n个数的子集的异或值有多少种
#include<bits/stdc++.h>
 
using namespace std;
 
typedef long long LL;
int const MOD = 2008;
int n, m;
 
struct Linear_Basis{
 
    LL d[61],p[61];
    int cnt, flag;
 
    Linear_Basis() {
        memset(d, 0 ,sizeof(d));
        memset(p, 0, sizeof(p));
        cnt = 0;  // 当前线性基内元素个数
        flag = 0;  // 不存在0
    }
 
    // 构造线性基:逐个元素插入
    bool insert(LL val) {
        for (int i = 60;i >= 0; i--) {
            if (val & (1ll << i)) {  // 判断当前i位能否插入
                if (!d[i]) {  // 如果当前i位没插入过元素
                    d[i] = val;  // 插入
                    return true;  // 插入成功
                }
                val ^= d[i];
            }
        }
        flag = 1;  // 存在0
        return false; // 插入失败
    }
 
    // 重构,使之形成对角矩阵
    void rebuild() {
        for (int i = 60;i >= 0; i--)  // 当前i位时,把后面的j位情况都和i位情况做异或
            for (int j = i - 1;j >= 0; j--)
                if (d[i] & (1ll << j))
                    d[i] ^= d[j];
        for (int i = 0; i <= 60; i++)  // 记录重构结果
            if (d[i])
                p[cnt++] = d[i];
    }
}lb;

int main(){
 
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        LL all = 0;
        for (int j = n - 1; j >= 0; --j) {
            char op;
            cin >> op;
            if (op == 'O') all += (1ll << j);
        }
        lb.insert(all);
    }
    lb.rebuild();
    cout << min(((1ll << lb.cnt)), (1ll << n)) % MOD;
    return 0;
}

acwing209装备购买
游戏有n件装备,每件装备有m个属性,如果第i件装备可以被其他任意件装备乘上对应系数表示出来,那么第i件装备不需要花钱购买,否则需要花钱。求最多可以购买多少件装备,同时求出在最多件装备的情况下的最小花费。
装备数N ~ 500,属性数M ~ 500

/*
借鉴线性基和高斯消元的方法,每次插入一个数字,就把它拿之前的元素消元
直到把当前元素消成当前位不存在的情况。
如果当前数组不断消元最后为0,那么这个数字可以被其他数组表示
如果当前数组消到第i位时,不存在这样的数组,那么这个数组可以插入线性基内,这个数组不可以被其他数组表示
需要花钱购买该装备
为了这个花费最小,需要贪心从小到大排序
*/

#include <bits/stdc++.h>

using namespace std;

int const N = 510;
double const eps = 1e-5;
int n, m;
double a[N][N];  // a表示线性基,第i个线性基的第j个元素为a[i][j]
struct COST {
    double z[N], cost;
    
    bool operator< (struct COST &t) const {
        return cost < t.cost;
    }
}cost[N];

// 插入线性基
bool insert(double x[]) {
    for (int i = 1; i <= m; ++i) {
        if (fabs(x[i]) < eps) continue;  // 当前x为0,那么不需要插入 
        if (fabs(a[i][i]) > eps) {  // 如果第i个线性基存在,那么需要对x进行消除操作,把第x[i]变成0
            double div = x[i] / a[i][i];  
            for (int j = i; j <= m; ++j) {  // 修改x数组
                x[j] -= div * a[i][j];
            }
        }
        else {  // 如果第i个线性基不存在
            for (int j = i; j <= m; ++j) a[i][j] = x[j];  // 插入到线性基内
            return true;
        }
    }
    return false;
}

int main() {
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            cin >> cost[i].z[j];
        }
    }
    
    for (int i = 1; i <= n; ++i) cin >> cost[i].cost;
    sort(cost + 1, cost + n + 1);
    
    // 不断插入线性基,如果能够插入,说明需要花费;否则,不需要花费
    double res1 = 0, res2 = 0;
    for (int i = 1; i <= n; ++i) {
        if (insert(cost[i].z)) {
            res1 ++;
            res2 += cost[i].cost;
        }
    }
    cout << res1 << " " << res2;
    return 0;
}
扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄