高斯消元题目小结

1、HDU - 3364 (https://vjudge.net/problem/HDU-3364)

题意:

n n n 盏灯,状态有亮 ( 1 ) (1) (1) 与暗 ( 0 ) (0) (0),有 m m m 个开关,每个开关按下会切换 k i k_i ki 盏灯的状态,每个开关至多按依次。现有 q q q 次询问,每次询问有多少种方案使得灯的状态为 s i s_i si ( n , m ≤ 50 , q ≤ 1000 ) (n, m \leq 50, q \leq 1000) (n,m50,q1000)

解题思路:

将第 i i i 个开关控制的灯表示为 n n n A i A_i Ai 向量,第 j j j 个分量为 1 1 1 表示按下 A i A_i Ai 后切换第 j j j 盏灯状态,否则不影响。用 x i x_i xi 表示第 i i i 个开关是否按下, S S S 表示末态,则表达式为
∑ i = 0 m − 1 x i A i ≡ S ( m o d 2 ) \sum\limits_{i = 0}^{m - 1} x_iA_i \equiv S \pmod{2} i=0m1xiAiS(mod2)
问题转化为求解 S S S 是否为向量组 A i A_i Ai 的线性组合(模 2 2 2 意义下),那么将 A i A_i Ai 作为矩阵列向量,即求解
[   A 0   A 1 ⋯ A m − 2   A m − 1     ] [ x 0 x 1 ⋮ x m − 2 x m − 1 ] ≡ [ s 0 s 1 ⋮ s n − 2 s n − 1 ] ( m o d 2 ) \left[\begin{matrix} \\ ~ \\ A_0 ~ A_1 \cdots A_{m - 2} ~ A_{m - 1} \\ ~ \\ ~\end{matrix}\right] \left[\begin{matrix} x_0 \\ x_1 \\ \vdots \\ x_{m - 2} \\ x_{m - 1} \end{matrix}\right] \equiv \left[\begin{matrix} s_0 \\ s_1 \\ \vdots \\ s_{n - 2} \\ s_{n - 1}\end{matrix}\right] \pmod{2}  A0 A1Am2 Am1  x0x1xm2xm1s0s1sn2sn1(mod2)
进行高斯消元,若有解,自由元个数为 x x x,答案为 2 x 2^x 2x,模 2 2 2 意义下可用位运算加速,则复杂度为 O ( q n m ) O(qnm) O(qnm)

参考代码:
#include<bits/stdc++.h>
 
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 5e1 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

struct Gauss{

    int piv[maxn];
    int solve(bitset<maxn> A[], int n, int m){

        int r = 0;
        for(int c = 0; c < m && r < n; ++c, ++r){

            int mx = r;
            for(int i = r; i < n; ++i){

                if(A[i][c]) { mx = i; break; }
            }
            if(!A[mx][c]) { --r; continue; }
            piv[r] = c;
            swap(A[r], A[mx]);
            for(int i = 0; i < n; ++i){

                if(i == r || !A[i][c]) continue;
                A[i] ^= A[r];
            }
        }
        for(int i = r; i < n; ++i) if(A[i][m]) return -1;
        return r;
    }
} gauss;

bitset<maxn> A[maxn], B[maxn];
int n, m, q;

int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    int t, cas = 0; cin >> t;
    while(t--){

        cin >> n >> m;
        for(int i = 0; i < n; ++i) A[i].reset();
        for(int i = 0; i < m; ++i){

            int k; cin >> k;
            while(k--){

                int tmp; cin >> tmp; --tmp;
                A[tmp][i] = 1;
            }
        }
        cin >> q;
        cout << "Case " << ++cas << ":\n";
        while(q--){

            for(int i = 0; i < n; ++i) B[i] = A[i];
            for(int i = 0; i < n; ++i){

                int tmp; cin >> tmp;
                B[i][m] = tmp;
            }
            int ret = gauss.solve(B, n, m);
            cout << (ret == -1 ? 0 : 1ll << (m - ret)) << "\n";
        }
    }
    return 0;
}

 
2、HDU - 4827 (https://vjudge.net/problem/HDU-4827)

题意:

给定一个有 n n n 个点、 m m m 条边的无向图,现要将结点编号 0 0 0 1 1 1,要求每个结点与偶数个邻接点编号相同,输出可行的一种结点编号方案。数据保证有解。 ( n ≤ 1000 , m ≤ 10000 ) (n \leq 1000, m \leq 10000) (n1000,m10000)

解题思路:

x i x_i xi 为结点编号, d e g [ i ] deg[i] deg[i] 为结点 i i i 的度,分类讨论一下。
x u = { 0 d e g [ u ]   %   2 = 0 , ⨁ v x v = 1 1 d e g [ u ]   %   2 = 0 , ⨁ v x v = 0 0 / 1 d e g [ u ]   %   2 = 1 , ⨁ v x v = 0 无 解 d e g [ u ]   %   2 = 1 , ⨁ v x v = 1 x_u = \begin{cases} 0 & deg[u]~\%~2 = 0, \bigoplus\limits_{v}x_v = 1 \\ 1 & deg[u]~\%~2 = 0, \bigoplus\limits_{v}x_v = 0 \\ 0 / 1 & deg[u] ~\%~2 = 1, \bigoplus\limits_{v}x_v = 0 \\ 无解 & deg[u] ~\%~2 = 1, \bigoplus\limits_{v}x_v = 1 \end{cases} xu=010/1deg[u] % 2=0,vxv=1deg[u] % 2=0,vxv=0deg[u] % 2=1,vxv=0deg[u] % 2=1,vxv=1
那么有
⨁ v x v = { 0 d e g [ u ]   %   2 = 0 x u ⊕ 1 d e g [ u ]   %   2 = 1 \bigoplus\limits_{v}x_v = \begin{cases} 0 & deg[u]~\%~2 = 0 \\ x_u \oplus 1 & deg[u]~\%~2 = 1 \end{cases} vxv={0xu1deg[u] % 2=0deg[u] % 2=1
对每个结点列出一条方程,高斯消元后确定主元编号,自由元编号统一为 0 0 0 即可。

参考代码:
#include<bits/stdc++.h>
 
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 1e3 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

struct Gauss{

    int piv[maxn];
    int solve(bitset<maxn> A[], int n, int m){

        int r = 0;
        for(int c = 0; c < m && r < n; ++c, ++r){

            int mx = r;
            for(int i = r; i < n; ++i){

                if(A[i][c]) { mx = i; break; }
            }
            if(!A[mx][c]) { --r; continue; }
            piv[r] = c;
            swap(A[r], A[mx]);
            for(int i = 0; i < n; ++i){

                if(i == r || !A[i][c]) continue;
                A[i] ^= A[r];
            }
        }
        for(int i = r; i < n; ++i) if(A[i][m]) return -1;
        return r;
    }
} gauss;

vector<int> G[maxn];
bitset<maxn> A[maxn];
char ans[maxn];
int deg[maxn], n, m;

int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    int t, cas = 0; cin >> t;
    while(t--){

        cin >> n >> m;
        for(int i = 0; i < n; ++i){

            G[i].clear();
            A[i].reset();
            deg[i] = 0;
        }
        for(int i = 0; i < m; ++i){

            int u, v; cin >> u >> v; --u, --v;
            G[u].pb(v), G[v].pb(u);
            ++deg[u], ++deg[v];
        }
        for(int i = 0; i < n; ++i){

            if(deg[i] & 1) A[i].set(i), A[i].set(n);
            for(auto &v : G[i]) A[i].set(v);
        }
        int rk = gauss.solve(A, n, n);
        for(int i = 0; i < n; ++i) ans[i] = '0';
        for(int i = 0; i < rk; ++i) ans[gauss.piv[i]] = A[i][n] + '0';
        ans[n] = 0;
        cout << "Case #" << ++cas << ":\n";
        cout << ans << "\n";
    }
    return 0;
}

 
3、HDU - 4592 (https://vjudge.net/problem/HDU-4592)

题意:

给定一个 n × n n×n n×n 的方格图,方格初始化为白色,翻转后为黑色。每次操作选定一个方格,将它及曼哈顿距离为 1 1 1 的方格全部翻转颜色。先给定目标方格图,问最少的操作次数,无解则输出 − 1 -1 1,数据共 m m m 组。 ( n ≤ 8 , m ≤ 1 0 6 ) (n \leq 8, m \leq 10^6) (n8,m106)

解题思路:

不看数据组数,就是典型的高斯消元,将二维方格图化成一维,问题转化为上述第一题。解方程 A x = b Ax = b Ax=b,除去高斯消元的复杂度,还需要枚举自由元的取值(判掉无解的情况,只选取主元并不一定最优,只能说主元所在列向量一定能够线性组合得到 b b b),单次复杂度已经很高。考虑到 A A A 只有八种,预处理出来发现除 n = 4 , 5 n = 4, 5 n=4,5 外, A A A 均满秩,且 n = 4 , 5 n = 4, 5 n=4,5 A A A 也接近满秩,复杂度瓶颈在高斯消元。
 
但满秩说明 A A A 可逆, x = A − 1 b x = A^{-1}b x=A1b,预处理出逆矩阵后,单次求解变为一次矩阵乘法复杂度,位运算优化后复杂度不高。对于 n = 4 , 5 n = 4, 5 n=4,5,直接枚举自由元取值,复杂度也不高。

参考代码:
#include<bits/stdc++.h>
 
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 65 * 2;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

struct Gauss{

    int piv[maxn], fre[maxn], tot;
    int solve(bitset<maxn> A[], int n, int m){

        int r = 0; tot = 0;
        for(int c = 0; c < m && r < n; ++c, ++r){

            int mx = r;
            for(int i = r; i < n; ++i){

                if(A[i][c]) { mx = i; break; }
            }
            if(!A[mx][c]){

                --r, fre[tot++] = c;
                continue;
            }
            piv[r] = c;
            swap(A[r], A[mx]);
            for(int i = 0; i < n; ++i){

                if(i == r || !A[i][c]) continue;
                A[i] ^= A[r];
            }
        }
        for(int i = r; i < n; ++i) if(A[i][m]) return -1;
        return r;
    }
} gauss;

const int xp[] = {-1, 1, 0, 0};
const int yp[] = {0, 0, 1, -1};
bitset<maxn> A[9][maxn], invA[9][maxn], AI[maxn], b, c;
int id[9][maxn][maxn];
int n;

const int maxs = 1e3 + 5;
char buf[maxs], *p1 = buf, *p2 = buf;
inline char fr(){

    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, maxs, stdin)) == p1 ? -1 : *p1++;
}
#define gc fr()
inline void read(int &x){

    char ch; while(!isdigit(ch = gc)); x = ch ^ 48;
    while(isdigit(ch = gc)) x = x * 10 + (ch ^ 48);
}

void init(bitset<maxn> A[], bitset<maxn> invA[], int id[][maxn], int n){

    int tot = 0;
    for(int i = 0; i < n; ++i){

        for(int j = 0; j < n; ++j) id[i][j] = tot++;
    }
    for(int i = 0; i < n; ++i){

        for(int j = 0; j < n; ++j){

            A[id[i][j]][id[i][j]] = 1;
            for(int k = 0; k < 4; ++k){

                int x = i + xp[k], y = j + yp[k];
                if(x < 0 || x >= n || y < 0 || y >= n) continue;
                A[id[x][y]][id[i][j]] = 1;
            }
        }
    }
    if(n == 4 || n == 5) return;
    for(int i = 0; i < tot; ++i) AI[i].reset();
    for(int i = 0; i < tot; ++i){

        for(int j = 0; j < tot; ++j) AI[i][j] = A[i][j];
        AI[i][i + tot] = 1;
    }
    gauss.solve(AI, tot, tot * 2);
    for(int i = 0; i < tot; ++i){

        for(int j = 0; j < tot; ++j) invA[i][j] = AI[i][j + tot];
    }
}

int main(){
 
    // ios::sync_with_stdio(0); cin.tie(0);
    for(int i = 1; i <= 8; ++i) init(A[i], invA[i], id[i], i);
    int t; read(t);
    while(t--){

        read(n);
        int tot = n * n;
        if(n != 4 && n != 5){

            b.reset();
            for(int i = 0; i < n; ++i){

                int tmp; read(tmp);
                for(int j = 0; j < n; ++j){
                    
                    b[id[n][i][j]] = (tmp >> j) & 1;
                }
            }
            int ret = 0;
            for(int i = 0; i < tot; ++i){

                ret += (invA[n][i] & b).count() & 1;
            }
            printf("%d\n", ret);
            continue;
        }
        for(int i = 0; i < tot; ++i) A[0][i] = A[n][i];
        for(int i = 0; i < n; ++i){

            int tmp; read(tmp);
            for(int j = 0; j < n; ++j){

                A[0][id[n][i][j]][tot] = (tmp >> j) & 1;
            }
        } 
        int rk = gauss.solve(A[0], tot, tot);
        if(rk == -1) { printf("-1\n"); continue; }
        int ret = inf, lim = 1 << gauss.tot;
        b.reset();
        for(int i = 0; i < tot; ++i) b[i] = A[0][i][tot];
        for(int i = 0; i < lim; ++i){

            int tmp = 0; c = b;
            for(int j = 0; j < gauss.tot; ++j){

                if((~i >> j) & 1) continue;
                ++tmp;
                int p = gauss.fre[j];
                for(int k = 0; k < tot; ++k){

                    if(A[0][k][p]) c.flip(k);
                }
            }
            tmp += c.count();
            ret = min(ret, tmp);
        }
        printf("%d\n", ret);
    }
    return 0;
}

4、HDU - 5833 (https://vjudge.net/problem/HDU-5833)

题意:

给定长度为 n n n 的数组 a a a,其中 a i a_i ai 不含大于 2000 2000 2000 的质因子,问有多少个非空集合满足集合中元素之积是平方数。 ( n ≤ 300 ) (n \leq 300) (n300)

解题思路:

a i a_i ai 分解成质因子之积,一个数是平方数当且仅当每种质因子有偶数个,故分奇偶考虑。问题转化为由 n n n 个二进制数,求异或和为 0 0 0 的方案数,列出方程组, A x = 0 Ax = 0 Ax=0 形式,求解个数,设自由元有 r r r 个,答案为 2 r − 1 2^r - 1 2r1

参考代码:
#include<bits/stdc++.h>
 
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 3e2 + 15;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

bitset<maxn> A[maxn];
int prime[2333], vis[2333];
int n, tot;

void init(){

    vis[1] = 1;
    for(int i = 2; i <= 2000; ++i){

        if(!vis[i]) prime[++tot] = i;
        for(int j = 1; j <= tot && i * prime[j] <= 2000; ++j){

            vis[i * prime[j]] = 1;
            if(i % prime[j] == 0) break;
        }
    }
}

struct Gauss{

    int piv[maxn], fre[maxn], tot;
    int solve(bitset<maxn> A[], int n, int m){

        int r = 0; tot = 0;
        for(int c = 0; c < m && r < n; ++c, ++r){

            int mx = r;
            for(int i = r; i < n; ++i){

                if(A[i][c]) { mx = i; break; }
            }
            if(!A[mx][c]) { --r, fre[tot++] = c; continue; }
            piv[r] = c;
            swap(A[r], A[mx]);
            for(int i = 0; i < n; ++i){

                if(i == r || !A[i][c]) continue;
                A[i] ^= A[r];
            }
        }
        for(int i = r; i < n; ++i) if(A[i][m]) return -1;
        return r;
    }
} gauss;

ll qpow(ll a, ll b){

    ll ret = 1;
    while(b){
        
        if(b & 1) ret = ret * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ret;
}

int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    init();
    int t, cas = 0; cin >> t;
    while(t--){

        cin >> n;
        for(int i = 0; i < tot; ++i){

            A[i].reset();
        }
        for(int i = 0; i < n; ++i){

            ll tmp; cin >> tmp;
            for(int j = 1; j <= tot; ++j){

                if(tmp % prime[j]) continue;
                int val = 0;
                while(tmp % prime[j] == 0) val ^= 1, tmp /= prime[j];
                A[j - 1][i] = val;
            }
        }
        int rk = gauss.solve(A, tot, n);
        ll ret = (qpow(2, n - rk) - 1 + mod) % mod;
        cout << "Case #" << ++cas << ":\n" << ret << "\n";
    }
    return 0;
}

 
5、HDU - 3976 (https://vjudge.net/problem/HDU-3976)

题意:

给定一个有 n n n 个结点、 m m m 个电阻的电路图,保证图连通。求电路等效电阻。 ( n ≤ 50 , m ≤ 2000 ) (n \leq 50, m \leq 2000) (n50,m2000)

解题思路:

定义结点 n n n 电势为 0 0 0,当其他结点电势确定时,整个电路的电流便确定下来, R e q = U e q I e q R_{eq} = \cfrac{U_{eq}}{I_{eq}} Req=IeqUeq。对每个结点列 K C L KCL KCL,则得到 n − 1 n - 1 n1 个线性无关的方程组,但条件不足,假定结点 1 1 1 电势为 U s U_s Us,那么可以确定所有结点电势,再以此求出等效电流即可。(强推 M I T MIT MIT 线代公开课,目前看了一半,教授讲的线性空间、基等概念很透彻,这道其实也作为应用讲解过)

参考代码:
#include<bits/stdc++.h>
 
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 5e2 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

struct Gauss{

    #define eps 1e-7
    int piv[maxn], fre[maxn], tot;
    int solve(double A[][maxn], int n, int m){

        int r = 0; tot = 0;
        for(int c = 0; c < m && r < n; ++c, ++r){

            int mx = r;
            for(int i = r + 1; i < n; ++i){

                if(fabs(A[i][c]) > fabs(A[mx][c])) mx = i;
            }
            if(fabs(A[mx][c]) < eps) { --r, fre[tot++] = c; continue; }
            piv[r] = c;
            for(int j = c; j <= m; ++j) swap(A[r][j], A[mx][j]);
            for(int i = 0; i < n; ++i){

                if(i == r) continue;
                double tmp = A[i][c] / A[r][c];
                for(int j = c; j <= m; ++j) A[i][j] -= tmp * A[r][j];
            }
        }
        for(int i = r; i < n; ++i) if(fabs(A[i][m]) > eps) return -1;
        for(int i = 0; i < r; ++i){

            double tmp = A[i][piv[i]];
            for(int j = piv[i]; j <= m; ++j) A[i][j] /= tmp;
        }
        return r;
    }
} gauss;

double A[maxn][maxn];
typedef pair<double, int> pdi;
vector<pdi> G[maxn];
int n, m;

int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    cout << fixed << setprecision(2);
    int t, cas = 0; cin >> t;
    while(t--){

        cin >> n >> m;
        for(int i = 0; i < n; ++i){

            G[i].clear();
            for(int j = 0; j < n; ++j) A[i][j] = 0;
        }
        while(m--){

            int u, v; double r; cin >> u >> v >> r; --u, --v;
            G[u].pb({r, v}), G[v].pb({r, u});
        }
        double Us = 1e10;
        for(int i = 1; i < n - 1; ++i){

            for(auto &e : G[i]){

                int v = e.second; double r = e.first;
                A[i - 1][i - 1] += 1 / r;
                if(v == 0) A[i - 1][n - 2] += Us / r;
                else if(v != n - 1) A[i - 1][v - 1] -= 1 / r;
            }
        }
        gauss.solve(A, n - 2, n - 2);
        double Is = 0, Req;
        for(auto &e : G[n - 1]){

            int v = e.second; double r = e.first;
            if(v == 0) Is += Us / r;
            else Is += A[v - 1][n - 2] / r;
        }
        Req = Us / Is;
        cout << "Case #" << ++cas << ": " << Req << "\n";
    }
    return 0;
}

 
6、HDU - 4818 (https://vjudge.net/problem/HDU-4818)

题意:

n n n 个人,即 n n n 个结点,朋友关系为单向边,共 m m m 条边,假设第 i i i 个人有 x i x_i xi R P RP RP,在每个时刻,每个人都会将他的 R P RP RP 均分单向传递给他的朋友,已知 ∑ i = 0 n − 1 x i = 1 \sum\limits_{i = 0}^{n - 1} x_i = 1 i=0n1xi=1,问有多少种 R P RP RP 分配方案使得每个时刻传递 R P RP RP 后,每个人的 R P RP RP 仍保持不变。此外,若从结点 n − 1 n - 1 n1 向外增加一条边,问连向谁能够使得 x n − 1 ′ > x n − 1 x_{n - 1}' \gt x_{n - 1} xn1>xn1,且 x n − 1 ′ x_{n - 1}' xn1 最大,优先连结点编号小的。 ( n ≤ 100 ) (n \leq 100) (n100)

解题思路:

上一道题的加强版,先接着上道题讲讲线代角度的理解。将图上的 n n n 个结点视为 n n n 维线性空间的一组基,图上的每条边对应为一个 n n n 维向量,那么针对图上的一个环而言,环上的向量必然线性有关(这很显然),并且,无环意味着边对应的向量组线性无关。那么图的边集对应的极大线性无关向量组的向量个数与图的生成树(或生成森林)是一样的,这可以通过判断图的连通分量数来确定。
 
每个结点处列出邻接边对应向量的线性组合,将涵盖所有边向量,其中极大线性无关向量组向量的个数,仍然不变。(每一条边总可以定向到某个结点)
 
回到题目,无穷解情况可以用连通性来辅助判断,当然也大可不必这样。
在前 n − 1 n - 1 n1 个结点处可以列出 n − 1 n - 1 n1 条方程,再加上 ∑ i = 0 n − 1 x i = 1 \sum\limits_{i = 0}^{n - 1}x_i = 1 i=0n1xi=1 作为最后一条, 共 n n n 条,依然是 A x = b Ax = b Ax=b 的方程形式,高斯消元判解情况,其实也可以发现并不存在无解情况, b = ( 0 , 0 , ⋯   , 1 ) T b = (0, 0, \cdots,1)^T b=(0,0,,1)T,该浮点数方程除了无穷解,就只有唯一解。
 
第二问着实推了一会,显然可以通过额外的 n n n 次消元来解决,但复杂度高达 O ( n 4 ) O(n^4) O(n4),连向单个结点的答案要在 O ( n 2 ) O(n^2) O(n2) 得到。首先考虑 u ( = n − 1 ) → v u(=n - 1) \rightarrow v u(=n1)v 边的增加会导致 A A A 如何变化,记 u u u 出度为 d d d,所有出点 v i v_i vi 对应得到的 R P RP RP 系数 − 1 d ( d + 1 ) - \cfrac{1}{d(d+1)} d(d+1)1,而 v v v 得到的 R P RP RP 系数 + 1 d + 1 + \cfrac{1}{d + 1} +d+11,在 A A A 矩阵中对应只改变第 n − 1 n - 1 n1 列,记该列向量为 c c c,记 A ′ = A + C A' = A + C A=A+C,其中 C = [   0   0   ⋯   c   ] C = [~0~0~\cdots~c~] C=[ 0 0  c ]
{ A x = b   ⇒   x = A − 1 b A ′ x ′ = ( A + C ) x ′ = b   ⇒   A − 1 ( A + C ) x ′ = ( E + A − 1 C ) x ′ = A − 1 b \begin{cases} Ax = b ~\Rightarrow ~ x = A^{-1}b\\ A'x' = (A + C)x' = b ~ \Rightarrow ~ A^{-1}(A + C)x' = (E + A^{-1}C)x' = A^{-1}b \end{cases} {Ax=b  x=A1bAx=(A+C)x=b  A1(A+C)x=(E+A1C)x=A1b
O ( n 3 ) O(n^3) O(n3) 处理出 A − 1 A^{-1} A1 A − 1 b A^{-1}b A1b 可以在 O ( n 2 ) O(n^2) O(n2) 得到,只需要求 x n − 1 ′ x'_{n - 1} xn1 的话,只需要得到 E + A − 1 C E + A^{-1}C E+A1C 的最后一行,而实际上该行也只有最后一列一个非零元素,恰可以在 O ( n 2 ) O(n^2) O(n2) 得到。那么 n n n 次判断,总复杂度 O ( n 3 ) O(n^3) O(n3)

参考代码:
#include<bits/stdc++.h>
 
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 5e2 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

struct Gauss{

    #define eps 1e-7
    int piv[maxn], fre[maxn], tot;
    int solve(double A[][maxn], int n, int m){

        int r = 0; tot = 0;
        for(int c = 0; c < m && r < n; ++c, ++r){

            int mx = r;
            for(int i = r + 1; i < n; ++i){

                if(fabs(A[i][c]) > fabs(A[mx][c])) mx = i;
            }
            if(fabs(A[mx][c]) < eps) { --r, fre[tot++] = c; continue; }
            piv[r] = c;
            for(int j = c; j <= m; ++j) swap(A[r][j], A[mx][j]);
            for(int i = 0; i < n; ++i){

                if(i == r) continue;
                double tmp = A[i][c] / A[r][c];
                for(int j = c; j <= m; ++j) A[i][j] -= tmp * A[r][j];
            }
        }
        for(int i = r; i < n; ++i) if(fabs(A[i][m]) > eps) return -1;
        for(int i = 0; i < r; ++i){

            double tmp = A[i][piv[i]];
            for(int j = piv[i]; j <= m; ++j) A[i][j] /= tmp;
        }
        return r;
    }
} gauss;

double A[maxn][maxn];
vector<int> G[maxn];
int out[maxn], n, m;

int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    int t; cin >> t;
    while(t--){

        cin >> n >> m;
        for(int i = 0; i < n; ++i){

            G[i].clear();
            out[i] = 0;
            for(int j = 0; j <= n * 2; ++j) A[i][j] = 0;
        }
        while(m--){

            int u, v; cin >> u >> v;
            G[u].pb(v), ++out[u];
        }
        for(int i = 0; i < n; ++i){

            if(i != n - 1) A[i][i] = -1;
            for(auto &v : G[i]){

                A[v][i] += 1.0 / out[i];
            }
        }
        for(int i = 0; i < n; ++i) A[n - 1][i] = 1;
        for(int i = 0; i < n; ++i) A[i][i + n] = 1;
        int rk = gauss.solve(A, n, n * 2);
        if(rk < n || gauss.piv[n - 1] >= n){

            cout << "INF\n";
            continue;
        }
        int p = -1; double mn = 1e10;
        for(int i = 0; i < n - 1; ++i){

            double tmp = A[n - 1][i + n] / (out[n - 1] + 1);
            for(auto &v : G[n - 1]){

                if(v == i) { tmp = 0; break; }
                tmp -= A[n - 1][v + n] / (out[n - 1] * (out[n - 1] + 1));
            }
            if(tmp < -eps && tmp + eps < mn) p = i, mn = tmp;
        }
        cout << "1 " << p << "\n";
    }  
    return 0;
}

 
U p d a t e : Update: Update: 由于 A A A 只有第 n − 1 n - 1 n1 列发生变化,那么将所有可能的方案产生的列向量都添加到 A A A 后面,一起消元,就可以 O ( n 3 ) O(n^3) O(n3) 得到答案。

#include<bits/stdc++.h>
 
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define sz(a) ((int)a.size())
#define pb push_back
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
const int maxn = 5e2 + 5;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

struct Gauss{

    #define eps 1e-7
    int piv[maxn], fre[maxn], tot;
    int solve(double A[][maxn], int n, int m){

        int r = 0; tot = 0;
        for(int c = 0; c < m && r < n; ++c, ++r){

            int mx = r;
            for(int i = r + 1; i < n; ++i){

                if(fabs(A[i][c]) > fabs(A[mx][c])) mx = i;
            }
            if(fabs(A[mx][c]) < eps) { --r, fre[tot++] = c; continue; }
            piv[r] = c;
            for(int j = c; j <= m; ++j) swap(A[r][j], A[mx][j]);
            for(int i = 0; i < n; ++i){

                if(i == r) continue;
                double tmp = A[i][c] / A[r][c];
                for(int j = c; j <= m; ++j) A[i][j] -= tmp * A[r][j];
            }
        }
        for(int i = r; i < n; ++i) if(fabs(A[i][m]) > eps) return -1;
        for(int i = 0; i < r; ++i){

            double tmp = A[i][piv[i]];
            for(int j = piv[i]; j <= m; ++j) A[i][j] /= tmp;
        }
        return r;
    }
} gauss;

double A[maxn][maxn];
vector<int> G[maxn];
int out[maxn], pre[maxn], n, m;

int fin(int x){

    return x == pre[x] ? x : pre[x] = fin(pre[x]);
}

int main(){
 
    ios::sync_with_stdio(0); cin.tie(0);
    int t; cin >> t;
    while(t--){

        cin >> n >> m;
        for(int i = 0; i < n; ++i){

            G[i].clear();
            out[i] = 0;
            for(int j = 0; j <= n * 2; ++j) A[i][j] = 0;
            pre[i] = i;
        }
        while(m--){

            int u, v; cin >> u >> v;
            G[u].pb(v), ++out[u];
            pre[fin(u)] = fin(v);
        }
        int tot = 0;
        for(int i = 0; i < n; ++i) tot += i == fin(i);
        if(tot != 1){

            cout << "INF\n";
            continue;
        }
        for(int i = 0; i < n; ++i){

            if(i != n - 1) A[i][i] = -1;
            for(auto &v : G[i]){

                A[v][i] += 1.0 / out[i];
            }
        }
        for(int i = 0; i < n; ++i) A[n - 1][i] = 1;
        for(int i = 0; i < n - 1; ++i){

            int flg = 0;
            A[i][i + n] += 1.0 / (out[n - 1] + 1);
            for(auto &v : G[n - 1]){

                if(v == i) {flg = 1; break;}
                A[v][i + n] -= 1.0 / (out[n - 1] * (out[n - 1] + 1));
            }
            if(flg) for(int j = 0; j < n; ++j) A[j][i + n] = 0;
            else for(int j = 0; j < n; ++j) A[j][i + n] += A[j][n - 1];
        }
        A[n - 1][2 * n - 1] = 1;
        int rk = gauss.solve(A, n, n * 2 - 1);
        if(rk == n - 1 || gauss.piv[n - 1] >= n){

            cout << "INF\n";
            continue;
        }
        int p = -1; double mn = 1e10;
        for(int i = 0; i < n - 1; ++i){

            double tmp = A[n - 1][i + n];
            if(tmp < eps) continue;
            if(tmp + eps < 1 && tmp + eps < mn) p = i, mn = tmp;
        }
        cout << "1 " << p << "\n";
    }  
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值