高斯消元

这篇博客探讨了如何使用高斯消元法来解决图论和概率中的复杂问题,包括计算节点概率、路径期望、最小化路径和期望步数等。通过实例解析了在不同题目中应用高斯消元法的过程,展示了这种方法在解决数学建模问题中的强大能力。
摘要由CSDN通过智能技术生成

高斯消元

15138 图图

题意: 给定一张n个点m条边的无向图,图中可以存在自环,任何点对(a, b)之间的路径只会存在一条。现在有一个人,他会向他所在的点随机选择一条出边移动,如果没有出边,就在原地不动。如果多条出边,那么选择出边的概率正比于这条出边的权值。现在不知道当前这个人在哪,但是我们知道这个人当前这轮在图上每个点的概率和走一步之后在每个点的概率相等。现在给定你这张图,问你这个人在每个点的概率是多少?如果无解,打印“Impossible”,如果多解,随便打印一个;如果唯一解,打印唯一解。 1 < = M < = 100 , 0 ≤ M ≤ 10000 , 0 ≤ a , b < N , 1 ≤ w ≤ 10 1<=M<=100, 0≤M≤10000,0≤a,b<N,1≤w≤10 1<=M<=100,0M10000,0a,b<N,1w10

题解: 设f[u]为当前这轮在u点的概率,g[u]为上一轮在u点的概率
那么转移方程为: f [ u ] = ∑ v − > u g [ v ] ∗ w i / d [ v ] f[u] = \sum_{v->u}g[v] * wi / d[v] f[u]=v>ug[v]wi/d[v]
而f[u] = g[u],则 f [ u ] = ∑ v − > u f [ v ] ∗ w i / d [ v ] f[u] = \sum_{v->u}f[v] * wi / d[v] f[u]=v>uf[v]wi/d[v]
那么可以列高斯消元得到在每个点的概率

代码:

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 105;
double a[MAXN][MAXN];  //增广矩阵
double x[MAXN];        //解集
int ans[MAXN];
double ispx = 1e-11;

int Gauss(int equ, int var) {
    int i, j, k;
    int max_r;  // 当前这列绝对值最大的行.
    int col;    //当前处理的列
    double ta;
    double temp;
    //转换为阶梯阵.
    col = 0;  // 当前处理的列
    for (k = 0; k < equ && col < var; k++, col++) {
        max_r = k;
        for (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 (j = 0; j < var + 1; j++) swap(a[k][j], a[max_r][j]);
        }
        if (fabs(a[k][col]) <= ispx) {
            continue;
        }
        for (int g = 0; g < equ; g++) {
            if (g != k) {
                ta = a[g][col] / a[k][col];
                for (int j = equ; j >= k; j--) {
                    a[g][j] = a[g][j] - a[k][j] * ta;
                }
            }
        }
    }
    return 0;
}

int main(void) {
    int i, j;
    int equ, var;
    scanf("%d%d", &equ, &var);
    memset(a, 0, sizeof(a));
    int x1, y1, val;
    for (j = 0; j < var; j++) {
        scanf("%d%d%d", &x1, &y1, &val);
        a[y1][x1] = 1.0 * val;
        ans[x1] += val;
    }
    for (int i = 0; i < equ; i++) {
        for (int j = 0; j < equ; j++) {
            if (!ans[j]) {
                a[j][j] = 1;
                ans[j] = 1;
            }
            a[i][j] = a[i][j] / (1.0 * ans[j]);
        }
    }
    for (int i = 0; i < equ; i++) {
        a[i][i] -= 1;
        a[i][equ] = 0;
    }
    for (int i = 0; i <= equ; i++) a[equ][i] = 1;
    int free_num = Gauss(equ + 1, equ);
    if (free_num == -1)
        puts("Impossible");
    else {
        for (int i = 0; i < equ; i++) {
            if (fabs(a[i][equ]) < ispx)
                printf("%.20lf\n", 0.0);
            else
                printf("%.20lf\n", a[i][equ] / a[i][i]);
        }
    }
    return 0;
}

20010 [HEOI2013]钙铁锌硒维生素

题意: 好难,鸽了

题解:

代码:

20086 [HNOI2011]XOR和路径

NC20086 [HNOI2011]XOR和路径

题意: 给定一个无向连通图,其节点编号为 1 到 N,其边的权值为非负整数。从1号节点开始,以相等的概率,随机选择与当前节点相关联的某条边,并沿这条边走到下一个节点,重复这个过程,直到走到 N 号节点为止。试求出一条从 1 号节点到 N 号节点的路径,使得该路径上经过的边的权值的“XOR和”最大。现在请你求出该算法得到的路径的“XOR 和”的期望值。 2 ≤ N ≤ 100 , M ≤ 10000 2≤N≤100,M≤10000 2N100M10000

题解:

代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 105;
const double eps = 1e-8;
double a[N][N];
int degree[N], n, m;
struct node {
    int to, w;
};
vector<node> pic[N];
int gauss() {
    int c, r;
    for (c = 0, r = 0; c < n; c++) {
        int t = r;
        for (int i = r; i < n; i++)
            if (fabs(a[i][c]) > fabs(a[t][c])) t = i;
        if (fabs(a[t][c]) < eps) continue;
        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];
        for (int i = r + 1; i < n; i++)
            if (fabs(a[i][c]) > eps)
                for (int j = n; j >= c; j--) a[i][j] -= a[r][j] * a[i][c];
        r++;
    }
    if (r < n) {
        for (int i = r; i < n; i++)
            if (fabs(a[i][n]) > eps) return 2;
        return 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() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i++) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        u--;
        v--;
        pic[u].push_back((node){v, w});
        degree[u]++;
        if (u != v) {
            pic[v].push_back((node){u, w});
            degree[v]++;
        }
    }
    double ans = 0;
    for (int i = 0; i <= 30; i++) {
        memset(a, 0, sizeof(a));
        for (int j = 0; j < n - 1; j++) {
            a[j][j] = 1.0;
            for (auto k : pic[j]) {
                int v = k.to;
                int w = k.w;
                if (w & (1 << i)) {  // 这一位是1
                    a[j][v] += 1.0 / degree[j];
                    a[j][n] += 1.0 / degree[j];
                } else  // 这一位是0
                    a[j][v] -= 1.0 / degree[j];
            }
        }
        a[n - 1][n - 1] = 1.0;  // 走到n点就停了
        gauss();
        ans += a[0][n] * (1 << i);
    }
    printf("%.3f", ans);
}

20105 [HNOI2013]游走

题意: 一个无向连通图,顶点从1编号到N,边从1编号到M。 小Z在该图上进行随机游走,初始时小Z在1号顶点,每一步小Z以相等的概率随机选 择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小Z 到达N号顶点时游走结束,总分为所有获得的分数之和。 现在,请你对这M条边进行编号,使得小Z获得的总分的期望值最小。
题解: 显然让期望经过次数越大的边编号越小即可,所以只要求出边的期望经过次数就能出解。
由于边数过大,但是边的期望经过次数可以用它连接的两个点的 期望次数/度数 之和得到,所以只要求出点的期望经过次数就能出解。对于每个一般的点y,它的期望经过次数为 f [ y ] = ∑ y − > x f [ x ] / d [ x ] 。 f[y]=∑_{y->x} f[x] / d[x]。 f[y]=y>xf[x]/d[x]
然后有两个特殊的点:1和n,由于1是起点,所以要算上初始经过的一次,在表达式的右边加1;由于n是终点,到达终点就不能够再经过其它边,所以按照上面的期望经过次数的计算方式,n的期望经过次数应该强制设置为0。(并非部分题解中的1)(所以其实上面的期望经过次数指的是“到达终点前的期望经过次数”,实际上也应该这样理解)
由于图是没有dp顺序的,所以需要使用高斯消元求出f[i]。求出所有点的期望经过次数,然后计算出边的经过次数,排序出解。
代码:

#include <bits/stdc++.h>

using namespace std;

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

int mp[N][N], n, m, d[N];
double a[N][N];
struct Edge {
    int u, v;
    double val;
    
    bool operator<(const Edge &w) const{
        return val > w.val;
    }
}edge[200010];

void gauss(int m, int n) {
    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];
    }
    
    // 唯一解,把解放到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 ;
}

int main() {
    cin >> n >> m;
    for (int i = 1, a, b; i <= m; ++i) {
        scanf("%d%d", &a, &b);
        edge[i].u = a, edge[i].v = b;
        mp[a][b] = mp[b][a] = 1;
        d[a]++, d[b]++;
    }
    
    for(int i = 1; i < n; i ++) {
        a[i - 1][i - 1] = 1;
        for(int j = 1; j <= n; j ++) if(mp[j][i]) a[i - 1][j - 1] = -1.0 / (double)d[j];
    }
    
    a[0][n] = a[n - 1][n - 1] = (double)1;
    
    gauss(n, n);
    
    for(int i = 1 ; i <= m ; i ++ ) edge[i].val = a[edge[i].u - 1][n] / (double)d[edge[i].u] + a[edge[i].v - 1][n] / (double)d[edge[i].v];
    sort(edge + 1, edge + m + 1);
    double res = 0;
    for (int i = 1; i <= m; ++i) res += edge[i].val * i;
    printf("%.3lf", res);
    return 0;
}

20167 [JSOI2008]球形空间产生器SPHERE

题意: 已经知道n维球体的球面上n+1个点的坐标,算出球心坐标。n~11
题解:
Screenshot_20200228_160243_com.huawei.notepad.jpg
代码:

#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++) {
        // 找出第一个元素最大的那一行
        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;
}

20336 [SDOI2010]外星千足虫

题意: 求一个异或型高斯消元的解。现在给定m个方程,n个未知数,问你是否解唯一。题目保证有解,如果多解,打印:Cannot Determine;否则打印最少需要前多少个方程才能得到唯一解,然后打印唯一解。 1 < = N < = 1 0 3 , 1 < = M < = 2 ∗ 1 0 3 1<=N<=10^3,1<=M<=2*10^3 1<=N<=103,1<=M<=2103

题解: 计算前多少个方程就能够得到唯一解,只需要找行首最大值时看下会找到哪行,然后每次找到的行首取max即可。如果找到的行首元素为0,说明出现多解,直接return即可。因为是01矩阵,也可以使用bitset优化。

代码:

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

const int MAXN = 2000 + 10;

char s[MAXN];
int equ, var, ans;
int a[MAXN][MAXN], x[MAXN];  // a存储增广矩阵, x存储解集
int free_x[MAXN];  //用来存储自由变元(多解枚举自由变元可以使用)
int free_num;  //自由变元的个数

//返回值为-1表示无解,为0是唯一解,否则返回自由变元个数
int Gauss() {
    int max_r, col, k;
    free_num = 0;
    for (k = 0, col = 0; k < equ && col < var; k++, col++) {
        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 (a[max_r][col] == 0) {
            k--;  // 如果把k--注释掉,能够保证自由元和每行式子的对应关系,但是无法保证无解的正确性。因此,如果题目保证有解,那么就可以注释掉这行,然后就可以暴力搜索自由元了
            free_x[free_num++] = col;  //这个是自由变元
            return 2;
        }
        ans = max(ans, max_r);
        if (max_r != k) {
            for (int j = col; j < var + 1; j++) swap(a[k][j], a[max_r][j]);
        }
        for (int i = k + 1; i < equ; i++) {
            if (a[i][col] != 0) {
                for (int j = col; j < var + 1; j++) a[i][j] ^= a[k][j];
            }
        }
    }
    if (k < var) return var - k;        //自由变元个数
                                        //唯一解,回代
    for (int i = var - 1; i >= 0; i--) {
        x[i] = a[i][var];
        for (int j = i + 1; j < var; j++) x[i] ^= (a[i][j] && x[j]);
    }
    return 0;
}

int main() {
    scanf("%d%d", &var, &equ);
    for (int i = 0; i < equ; i++) {
        scanf("%s", s);
        for (int j = 0; j < var; j++) a[i][j] = s[j] - '0';
        scanf("%s", s);
        a[i][var] = s[0] - '0';
    }
    ans = -1;
    int t = Gauss();
    if (t == 0) {
        cout << ans + 1 << endl; 
        for (int i = 0; i < var; i++) {
            if (x[i]) puts("?y7M#");
            else puts("Earth");
        }
    } else if (t > 0) puts("Cannot Determine");
    return 0;
}

使用bieset优化

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

const int MAXN = 2000 + 10;

char s[MAXN];
int equ, var, ans;
bitset<MAXN> a[MAXN];
int x[MAXN];  // a存储增广矩阵, x存储解集
int free_x[MAXN];  //用来存储自由变元(多解枚举自由变元可以使用)
int free_num;  //自由变元的个数

//返回值为-1表示无解,为0是唯一解,否则返回自由变元个数
int Gauss() {
    int max_r, col, k;
    free_num = 0;
    for (k = 0, col = 0; k < equ && col < var; k++, col++) {
        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 (a[max_r][col] == 0) {
            k--;  // 如果把k--注释掉,能够保证自由元和每行式子的对应关系,但是无法保证无解的正确性。因此,如果题目保证有解,那么就可以注释掉这行,然后就可以暴力搜索自由元了
            free_x[free_num++] = col;  //这个是自由变元
            return 2;
        }
        ans = max(ans, max_r);
        if (max_r != k) {
            swap(a[k], a[max_r]);
        }
        for (int i = k + 1; i < equ; i++) {
            if (a[i][col] != 0) a[i] ^= a[k];
        }
    }
    if (k < var) return var - k;        //自由变元个数
                                        //唯一解,回代
    for (int i = var - 1; i >= 0; i--) {
        x[i] = a[i][var];
        for (int j = i + 1; j < var; j++) x[i] ^= (a[i][j] && x[j]);
    }
    return 0;
}

int main() {
    scanf("%d%d", &var, &equ);
    for (int i = 0; i < equ; i++) {
        scanf("%s", s);
        for (int j = 0; j < var; j++) a[i][j] = s[j] - '0';
        scanf("%s", s);
        a[i][var] = s[0] - '0';
    }
    ans = -1;
    int t = Gauss();
    if (t == 0) {
        cout << ans + 1 << endl; 
        for (int i = 0; i < var; i++) {
            if (x[i]) puts("?y7M#");
            else puts("Earth");
        }
    } else if (t > 0) puts("Cannot Determine");
    return 0;
}

20394 [SDOI2017]硬币游戏

题意: 好难,鸽了

题解:

代码:

20575 [SDOI2012]走迷宫

题意: 迷宫可以视为N个点M条边的有向图,Morenan处于起点S,迷宫的终点设为T。他只会从一个点出发随机沿着一条从该点出发的有向边,到达另一个点。这样,Morenan走的步数可能很长,也可能是无限,更可能到不了终点。若到不了终点,则步数视为无穷大。求出所走步数的期望值。 1 ≤ n < = 1 0 4 , 0 ≤ m ≤ 1 0 6 1≤n<=10^4,0\leq m \leq 10^6 1n<=1040m106

题解:

参考:https://blog.csdn.net/Jaihk662/article/details/79134159 的思路和代码

①输入边,建图和反相图,无视掉以终点为起点的边
②如果起点终点不连通或者某个点起点能到,但这个点到不了终点就是INF,对原图和反相图分别进行一次DFS就可以判断出来
③对整个有向图,做tarjan进行缩点,得到dag
④超级点与超级点间按照反拓扑序进行更新期望,超级点内部按照高斯消元求出期望

高斯消元方法:每个点都可以列出一个方程:F[i] = ∑(F[j]/out[i], 存在边(i, j))+1
其中F[i]为从i点走到终点的期望步数,out[i]为i点的出度

代码:

#include <bits/stdc++.h>
using namespace std;
stack<int> st;
vector<int> G[10005], GF[10005], Bel[10005];
int S, T, out[10005], she[10005], ok[10005], vis[10005], cnt, num, scc[10005],
    tim[10005], dfn[10005];
double ave[10005], Jz[105][105], c[105];
void Jud1(int u) {
    int i, v;
    ok[u]++, vis[u] = 1;
    for (i = 0; i < G[u].size(); i++) {
        v = G[u][i];
        if (vis[v]) continue;
        Jud1(v);
    }
}
void Jud2(int u) {
    int i, v;
    ok[u]++, vis[u] = 1;
    for (i = 0; i < GF[u].size(); i++) {
        v = GF[u][i];
        if (vis[v] || ok[v] == 0) continue;
        Jud2(v);
    }
}
void Trajan(int u) {
    int i, v;
    vis[u] = 1;
    st.push(u);
    tim[u] = dfn[u] = ++cnt;
    for (i = 0; i < G[u].size(); i++) {
        v = G[u][i];
        if (vis[v] == 0) {
            Trajan(v);
            dfn[u] = min(dfn[u], dfn[v]);
        } else if (scc[v] == 0)
            dfn[u] = min(dfn[u], tim[v]);
    }
    if (dfn[u] == tim[u]) {
        num++;
        while (st.empty() == 0) {
            v = st.top();
            st.pop();
            scc[v] = num;
            Bel[num].push_back(v);
            if (v == u) break;
        }
    }
}
void Gauss(int n, int m) {
    int i, j, p, q, r;
    p = q = 1;
    while (p <= n && q <= m) {
        r = p;
        for (i = p + 1; i <= n; i++) {
            if (fabs(Jz[i][q]) > fabs(Jz[p][q])) r = i;
        }
        for (i = q; i <= m; i++) swap(Jz[r][i], Jz[p][i]);
        swap(c[r], c[p]);
        c[p] /= Jz[p][q];
        for (i = q + 1; i <= m; i++) Jz[p][i] /= Jz[p][q];
        for (i = 1; i <= n; i++) {
            if (Jz[i][q] && i != p) {
                for (j = q + 1; j <= m; j++) Jz[i][j] -= Jz[p][j] * Jz[i][q];
                c[i] -= c[p] * Jz[i][q];
                Jz[i][q] = 0;
            }
        }
        q++, p++;
    }
}
int main(void) {
    int i, x, y, n, m, j, k;
    scanf("%d%d%d%d", &n, &m, &S, &T);
    for (i = 1; i <= m; i++) {
        scanf("%d%d", &x, &y);
        if (x == T) continue;
        G[x].push_back(y);
        GF[y].push_back(x);
        out[x]++;
    }
    Jud1(S);
    memset(vis, 0, sizeof(vis));
    Jud2(T);
    for (i = 1; i <= n; i++) {
        if (ok[i] == 1) {
            printf("INF\n");
            return 0;
        }
    }
    if (S == T) {
        printf("0.000\n");
        return 0;
    }
    memset(vis, 0, sizeof(vis));
    Trajan(S);
    for (i = scc[T] + 1; i <= scc[S]; i++) {
        memset(Jz, 0, sizeof(Jz));
        memset(c, 0, sizeof(c));
        for (j = 0; j < Bel[i].size(); j++) she[Bel[i][j]] = j + 1;
        for (j = 0; j < Bel[i].size(); j++) {
            x = Bel[i][j];
            Jz[j + 1][j + 1] = -1;
            c[j + 1] -= 1 + ave[x] / out[x];
            for (k = 0; k < GF[x].size(); k++) {
                y = GF[x][k];
                if (scc[y] == scc[x]) Jz[she[y]][she[x]] += 1.0 / out[y];
            }
        }
        Gauss(j, j);
        for (j = 0; j < Bel[i].size(); j++) {
            x = Bel[i][j];
            for (k = 0; k < GF[x].size(); k++) {
                y = GF[x][k];
                if (scc[y] != scc[x]) ave[y] += c[she[x]];
            }
        }
    }
    printf("%.3f\n", fabs(c[she[S]]));
    return 0;
}

24629 Driving Out the Piggies

题意: 有一个 n 个点 m 条边的无向图,节点 1 有一个炸弹,在每个单位时间内,这个炸弹有 P Q \frac{P}{Q} QP 的概率在这个节点炸掉,有 1 − P Q 1-\frac{P}{Q} 1QP 的概率等概率选择一条与当前节点相连的边到其他的节点上。求炸弹在每个节点上爆炸的概率。 2 ≤ n ≤ 300 , 1 ≤ m ≤ 44850 , 1 ≤ P , Q ≤ 1 0 6 2≤n≤300,1\le m\le 44850,1\le P,Q\le 10^6 2n3001m448501P,Q106

**题解: ** 设第i个点的期望经过次数为 f [ i ] f[i] f[i], 那么第i个点爆炸的概率为 f [ i ] ∗ P Q f[i] * \frac{P}{Q} f[i]QP
对于 f [ i ] f[i] f[i]有如下的式子:

f [ u ] = 1 ,   i = 1 f[u] = 1,\ i = 1 f[u]=1, i=1

f [ u ] = ∑ u − > v ( 1 − P Q )   ∗   f [ v ] d e g [ v ] ,   o t h e r w i s e f[u] = \sum_{u->v}(1-\frac{P}{Q})\ *\ \frac{f[v]}{deg[v]},\ otherwise f[u]=u>v(1QP)  deg[v]f[v], otherwise

代码:

#include <bits/stdc++.h>

#define int long long
#define debug(x) cout << #x << " = " << x << endl;
using namespace std;

int const MAXN = 3e3 + 10;
const double eps = 1e-10;
int n, m, T, p, q;
int edge[MAXN][MAXN], d[MAXN];
double a[MAXN][MAXN];

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 (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;
}

signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cin >> n >> m >> p >> q;
    double P = 1.0 - (double)p / (double)q;
    for (int i = 1, a, b; i <= m; ++i) {
        cin >> a >> b;
        a--, b--;
        edge[a][b] = edge[b][a] = 1;
        d[a]++, d[b]++;
    }
    for (int i = 0; i < n; ++i) {
        a[i][i] = 1.0;
        for (int j = 0; j < n; ++j) {
            if (edge[i][j]) a[i][j] -= P / (double)d[j]; 
        }
    }
    a[0][n] = 1.0;
    m = n;
    gauss();
    for (int i = 0; i < n; ++i) printf("%.10lf\n", a[i][n] * (double)p / (double)q);
    return 0;
}

24860 Lights

题意: 牛棚中一共有N(1 <= N <= 35)盏灯,编号为1到N。这些灯被置于一个非常复杂的网络之中。有M(1 <= M <= 595)条很神奇的无向边,每条边连接两盏灯。 每盏灯上面都带有一个开关。当按下某一盏灯的开关的时候,这盏灯本身,还有所有有边连向这盏灯的灯的状态都会被改变。问最少要按下多少个开关,才能把所有的灯都给重新打开。 数据保证至少有一种按开关的方案,使得所有的灯都被重新打开。

题解: 和上面的开关问题一样,列出方程后,dfs暴力搜索自由元的选择情况。这里注意,板子里面k–要注释掉,因为题目保证有解,同时如果不注释掉,会改变原来的所影响的那一行的开关状态

代码:

#include <bits/stdc++.h>

using namespace std;

const int MAXN = 400;
int equ, var;
int a[MAXN][MAXN], x[MAXN];  // a存储增广矩阵, x存储解集
int free_x[MAXN];  //用来存储自由变元(多解枚举自由变元可以使用)
int free_num;  //自由变元的个数

//返回值为-1表示无解,为0是唯一解,否则返回自由变元个数
int Gauss() {
    int max_r, col, k;
    free_num = 0;
    for (k = 0, col = 0; k < equ && col < var; k++, col++) {
        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 (a[max_r][col] == 0) {
            //k--;
            free_x[col] = 1;  //这个是自由变元
            free_num++;
            continue;
        }
        if (max_r != k) {
            for (int j = col; j < var + 1; j++) swap(a[k][j], a[max_r][j]);
        }
        for (int i = k + 1; i < equ; i++) {
            if (a[i][col] != 0) {
                for (int j = col; j < var + 1; j++) a[i][j] ^= a[k][j];
            }
        }
    }
    for (int i = k; i < equ; i++)
        if (a[i][col] != 0) return -1;  //无解
    if (free_num) return free_num;        //自由变元个数
                                        //唯一解,回代
    for (int i = var - 1; i >= 0; i--) {
        x[i] = a[i][var];
        for (int j = i + 1; j < var; j++) x[i] ^= (a[i][j] && x[j]);
    }
    return 0;
}

int sw[MAXN];
int ans = 0x3f3f3f3f;
void dfs(int xx, int tot) {
    if (tot > ans) return;  //剪枝
    if (xx == -1) {
        ans = min(ans, tot);
        return;
    }
    if (free_x[xx] == 0) {  //如果消元后系数为1,则用高斯消元的判断方法进行判断
        sw[xx] = a[xx][var];
        for (int j = var - 1; j > xx; j--) {
            sw[xx] ^= (sw[j] & a[xx][j]);
        }
        dfs(xx - 1, tot + sw[xx]);
    } else {  //如果为0就随便定一个
        sw[xx] = 0;
        dfs(xx - 1, tot);
        sw[xx] = 1;
        dfs(xx - 1, tot + 1);
    }
}

int main() {
    vector<int> v[50];
    int m;
    cin >> equ >> m;
    var = equ;
    for (int i = 1, a, b; i <= m; ++i) {
        cin >> a >> b;
        a--, b--;
        v[a].push_back(b), v[b].push_back(a);
    }
    for (int i = 0; i < equ; ++i) v[i].push_back(i), a[i][var] = 1;
    for (int i = 0; i < equ; ++i) 
        for (auto j: v[i]) a[i][j] = 1;
    free_num = Gauss();
    if (free_num > 0) {
        dfs(var - 1, 0);
        cout << ans;
    } else {
        int res = 0;
        for (int i = 0; i < var; i++) if (x[i]) res++;
        cout << res;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值