HDU 4408 árbol de expansión mínimo (recuento mínimo de árboles de expansión)

Pregunta: Calcule el número mínimo de árboles de expansión.

Solución del problema: número mínimo de árboles de expansión
Si no consideramos el mínimo, sabemos que el teorema del árbol de matriz puede calcular el número de árboles de expansión. Bien podríamos considerar primero el más pequeño.

En Kruskal, cada vez que seleccionamos el borde más corto para conectar dos bloques conectados, para un conjunto de bordes de igual longitud, sin importar cuál elijamos primero, la conectividad final G0 es la misma, es decir, todos los bordes de este conjunto La conexión después de la inserción es la misma que G0, pero el gráfico es diferente.

A partir de esto, podemos encontrar que podemos dividir los bordes de la misma longitud en una etapa, y esta etapa es completamente independiente de las etapas posteriores. Luego multiplicamos el número de planes en cada etapa, que en realidad es la respuesta.

Considere una sola etapa, y los bloques conectados formados por las etapas anteriores se pueden reducir, y las longitudes de los bordes son iguales, y se debe garantizar que el recuento de árboles de expansión sea el árbol de expansión mínimo.

Y el conjunto de verificación fa es el bloque de conexión al que pertenece el nodo antes de la etapa actual, y el bloque de conexión en el que se encuentra después de que el borde de ka esté conectado en la etapa actual. Se utilizan dos porque cuando se conecta un mismo borde se considera la conexión de la etapa anterior, conectamos todas las que cumplen con los requisitos y luego ejecutamos el conteo.

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<fstream>
#include<set>
#include<map>
#include<sstream>
#include<iomanip>
#define ll long long
using namespace std;
int n, m, MOD, u, v, w, g[111][111], fa[111], ka[111], vis[111];
struct node {
    
    
    int u, v, w;
    bool operator<(const node& x)const {
    
    
        return w < x.w;
    }
}edge[1111];
int Find(int x, int Parent[]) {
    
    
    return Parent[x] == x ? x : Parent[x] = Find(Parent[x], Parent);
}
vector<int> gra[111];
struct Matrix {
    
    
    int mat[111][111];
    ll ans = 1;
    void init() {
    
    
        memset(mat, 0, sizeof(mat));
    }
    int det(int n) {
    
    
        ll res = 1;
        for (int i = 1; i <= n; i++) {
    
    
            if (!mat[i][i]) {
    
     //若果对角线元素为0,把此行都一都移到下一行去
                bool flag = false;
                for (int j = i + 1; j <= n; j++) {
    
     //从i+1行开始找i列中的第一个不为0的元素,与现在的行交换
                    if (mat[j][i]) {
    
    //找到了该列不为0的元素,
                        flag = 1; //标记,交换
                        for (int k = i; k <= n; k++) swap(mat[i][k], mat[j][k]);
                        res = -res;// 换行系数变为负数
                        break; //退出.
                    }
                }
                if (!flag) return 0; //这一行全部为0,行列式值为0
            }
            for (int j = i + 1; j <= n; j++) {
    
    
                while (mat[j][i]) {
    
     //从下面的行找一个不为0的元素与第i行进行消元
                    ll t = mat[i][i] / mat[j][i];
                    for (int k = i; k <= n; k++) {
    
    
                        mat[i][k] = (mat[i][k] - t * mat[j][k]) % MOD;
                        swap(mat[i][k], mat[j][k]);//消元后,把0的行换到下面来。
                    }
                    res = -res;
                }
            }
            res *= mat[i][i];//对角线元素相乘
            res %= MOD;
        }
        return (res + MOD) % MOD;
    }
    void matrix_tree() {
    
    
        for (int i = 0; i < n; i++) gra[i].clear();
        for (int i = 0; i < n; i++) {
    
    
            if (!vis[i]) continue;  //考虑当前阶段的点
            gra[Find(i, ka)].push_back(i);  //有多个连通块,我们放进vector
            vis[i] = 0;
        }
        for (int i = 0; i < n; i++) {
    
    
            init();
            for (int j = 0; j < gra[i].size(); j++) {
    
    
                for (int k = j + 1; k < gra[i].size(); k++) {
    
    
                    int u = gra[i][j];
                    int v = gra[i][k];
                    mat[j][k] -= g[u][v];     //有重边
                    mat[k][j] -= g[u][v];
                    mat[j][j] += g[u][v];
                    mat[k][k] += g[u][v];
                }
            }
            ans = (ans * det(gra[i].size() - 1)) % MOD;
            //更新连通块中每个点的fa,保存当前阶段连通情况(只考虑点,不考虑图是怎样的 )
            for (int j = 0; j < gra[i].size(); j++) fa[gra[i][j]] = i;
        }
        for (int i = 0; i < n; i++) ka[i] = Find(i, fa);
    }
}ma;
int main() {
    
    
	while (~scanf("%d%d%d", &n, &m, &MOD) && n) {
    
    
        for (int i = 0; i < m; i++) {
    
    
            scanf("%d%d%d", &u, &v, &w);
            edge[i] = {
    
     u - 1, v - 1, w };
        }
        sort(edge, edge + m);
        memset(g, 0, sizeof(g));
        for (int i = 0; i < n; i++) ka[i] = fa[i] = i;
        ma.ans = 1;
        for (int i = 0; i <= m; i++) {
    
    
            if (i && edge[i].w != edge[i - 1].w || i == m) {
    
    
                ma.matrix_tree();  //相同长度的边都连上了
            }
            int pa = Find(edge[i].u, fa);  //连接缩点之后的两个点
            int pb = Find(edge[i].v, fa);
            if (pa == pb) continue;   //kruskal只考虑上个阶段两个不同连通块
            vis[pa] = vis[pb] = 1;    //标记是属于当前阶段的点
            ka[Find(pa, ka)] = Find(pb, ka);  //连接两个连通块
            g[pa][pb]++;
            g[pb][pa]++;
        }
        int flag = 1;
        for (int i = 1; i < n; i++) if (fa[i] != fa[i - 1]) flag = 0;
        printf("%lld\n", flag ? ma.ans % MOD : 0);
	}
	return 0;
}

/*
5 10 12
2 5 3
2 4 2
3 1 3
3 4 2
1 2 3
5 4 3
5 1 3
4 1 1
5 3 3
3 2 3
0 0 0
*/

Supongo que te gusta

Origin blog.csdn.net/qq_43680965/article/details/107848533
Recomendado
Clasificación