生成树相关模板

基本的最小生成树的Kruskal, Prime算法不再阐述.

有向图最小树形图
以某一根节点出发,可以到达所有节点,且边权总和最小的子图.这里模板使用的时朱刘算法;
下面时模板代码:

/*
朱刘算法:
1.找所有节点的最小入边,标记
2.累加这些边的权值,如果这时没有环,则得到了最优解. 有环则进行下一
3.将环缩点,所有连接此环的入边权值减少当前这个终节点的最小入度边权值(可能减成负的)
4.返回1

复杂度:O(nm)

*/

//洛谷模板题 P4716
const int N = 2e5+5;
const int inf = 0x3f3f3f3f;
struct Node
{
    int oldu, oldv;
    int u, v, w;
}e[N];
int n, m, rt;
int pre[N], ID[N], vis[N];
int in[N];
int Directed_MST(int root, int n, int m)
{
    int i, oldroot = root; //求最小根节点, 在定根时可以删去.
    ll ret = 0;
    while(1)
    {
        //1. 找到最小入边.
        for (int i = 1; i <= n; i++) in[i] = inf;
        for (int i = 1; i <= m; i++)
        {
            int u = e[i].u; int v = e[i].v;
            if (u != v && e[i].w < in[v])
            {
                pre[v] = u; in[v] = e[i].w;
                
                if (e[i].oldu == oldroot) rt = e[i].oldv;
                //求最小根节点, 在定根时可以删去.
            }
        }
        for (int i = 1; i <= n; i++)
        {
            if (i == root) continue;
            if (in[i] == inf) return -1; //不存在, 无解退出
        }

        //2. 找环
        int nn = 0;
        memset(ID, -1, sizeof(ID));
        memset(vis, -1, sizeof(vis));
        in[root] = 0;
        for (int i = 1; i <= n; i++)
        { //标记每一个环
            ret += in[i]; //累加答案(等待后续处理), 虽然是累加,但是in[i]可能时负的
            int v = i;
            while(v != root && ID[v] == -1 && vis[v] != i)
                vis[v]=i, v = pre[v]; //回溯标记点
            if (v != root && ID[v] == -1)
            { //找到环 因为ID[v] == -1, 所以v没有成环过,所以只能是vis[v] == v,回溯时找到了环
                ID[v] = ++nn;
                for (int u = pre[v]; u != v; u = pre[u])
                    ID[u] = nn;
                //标记环
            }
        }
        if (nn == 0) break; //无环, 找到最优解退出
        for (int i = 1; i <= n; i++) if (ID[i] == -1)
            ID[i] = ++nn; // 未成环节点返还
        for (int i = 1; i <= m; i++)
        {   //缩点
            int u = e[i].u;
            int v = e[i].v;
            e[i].u = ID[u];
            e[i].v = ID[v];
            if (u != v) e[i].w -= in[v];
            // 如果接下来想要选择i号边, 势必会解除现有最小入边的选择,
            //所以以但选择i节点,就要断链旧入边,故权值-=in[v]
        }
        n = nn;
        root = ID[root];
    }
    return ret;
}
int main()
{
    int n, m, r;
    cin >> n >> m >> r;
    int sum = 0;
    for (int i = 1; i <= m; i++)
    {
        scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w);
        e[i].oldu = e[i].u, e[i].oldv = e[i].v;
        sum += e[i].w;
    }

/*
    不定根求法:
    建立超级根节点: 想每个根节点连有向边,权值大于原图所有边权值之和
    for (int i = 1; i <= n; i++)
    {
        e[++m].u = n+1; e[m].v = i; e[m].w = sum+1;
        e[m].oldu = e[m].u; e[m].oldv = e[m].v;
    }
    n++;
    cout << Directed_MST(n, n, m);
    //此时最小根节点时rt.
*/

    printf("%d\n", Directed_MST(r, n, m));
    return 0;
}

K度限制生成树
求一个无向图的最小生成树,不过这里有一个节点限制最大度数不可以超过K,这时我们可以求出刨去root节点后的MST,然后尝试连边再破环,从m度限制生成树推定到K度限制生成树.

/*
K度限制MST
将有限制的那个节点设置为root节点
先通过Kruskal跑出除了root节点的MST, 这时, 原图刨去root可能会变成一个
m个联通快的子图. 这时如果m > k 肯定无解, 否则
我们选择m个root的边, 将这个m个子最小生成树连接起来, 这时我们得到了限制
为m度的MST, 这时我们如果我们从子图中想root连一条边, 并把一个非root端点
边去掉, 如果找到最佳方案, 我就就找到了m+1度MST, 依次类推,直至寻找到限制
K度MST
*/


//以 POJ 1639 为模板
struct Node
{
    int x, y, w;
    friend bool operator < (const Node & a, const Node &b)
    {
        return a.w < b.w;
    }
}ed[N], ee;// 用于Kruskal找除了root之外的MST,所存的原题
int n, m, k, root;
map<string, int> mp;
vector<Node> e[N], rt; // 存储生成树.
bool mark[N]; // 标记root选用的边
void add(int x, int y, int w)
{
    ee.y = y; ee.w = w; e[x].push_back(ee);
    ee.y = x; ee.w = w; e[y].push_back(ee);
}
int fa[N];
int fi(int x)  {if (x == fa[x]) return x; return fa[x] = fi(fa[x]); }
bool merge(int x, int y)
{
    x = fi(x), y = fi(y);
    if (x == y) return 0;
    fa[x] = y; return 1;
}
// Kruskal主过程------用于寻找除了root外的MST
int kruskal()
{
    int x, y, res = 0;
    for (int i = 0; i <= n; i++) fa[i] = i;
    sort(ed+1, ed+1+m);
    for (int i = 1; i <= m; i++)
    {
        x = ed[i].x; y = ed[i].y;
        if (x == root || y == root)
        {
            if (y == root) swap(ed[i].x, ed[i].y);
            rt.push_back(ed[i]);
        }
        else if (merge(x, y))
        {
            res += ed[i].w;
            add(x, y, ed[i].w);
        }
    }
    return res;
}
//加边去环主过程------从m度MST扩展至k度MST
bool vis[N];
Node best[N]; // best[i] : 记录从root到i的最大边
void dfs(int x, Node f) // 找最大边O(n)
{
    if (vis[x]) return;
    vis[x] = 1; best[x] = f;
    for (int i = 0; i < e[x].size(); i++)
    {
        Node ff = f;
        int y = e[x][i].y, w = e[x][i].w;
        if (w > ff.w) ff.x = x, ff.y = y, ff.w = w;
        dfs(y, ff);
    }
    vis[x] = 0;
}
void Remove(int x, int y) //删除最大边
{ 
    for (int i = 0; i < e[x].size(); i++) if (e[x][i].y == y)
    { e[x].erase(e[x].begin() + i); return; }
}
int solve(int res)
{
    int x = root, y, w;
    int d = 0;
    for (int i = 0; i < rt.size(); i++)
    {
        y = rt[i].y, w = rt[i].w;
        if (merge(x, y))
        {
            d++;  res += w;
            add(x, y, w);
            mark[i] = 1;
        }
    } // 连接root和m个子图
    while(d < k) // 当度数d仍小于k
    {
        int tmp = 0, j = 0; 
        Node max_e;
        ee.w = -1; dfs(root, ee); //最大边求出
        for (int i = 0; i < rt.size(); i++)
        { // 寻找用于替换的边
            if (mark[i]) continue; // 已选. pase掉
            y = rt[i].y; w = rt[i].w;
            if (tmp < best[y].w - w)
            {
                tmp = best[y].w - w;
                j = i; max_e = best[y];
            } // 找到差值最大的方案
        }
        if (tmp == 0) break; // 差值为0, 现在已经是k度MST了
        mark[j] = 1; // 开始替换 
        Remove(max_e.x, max_e.y);
        Remove(max_e.y, max_e.x);
        e[root].push_back(rt[j]);
        d++, res -= tmp;
    }
    return res;
}
void init()
{
    mp.clear(); mp["Park"] = n = 1;
    for (int i = 0; i <= m << 1; i++) e[i].clear();
    rt.clear();
    memset(mark, 0, sizeof(mark));
    memset(vis, 0, sizeof(vis));
}
int main()
{
    char s1[32], s2[32];
    int x, y, w;
    while(scanf("%d", &m) != EOF)
    {
        init();
        for (int i = 1; i <= m; i++)
        {
            scanf("%s%s%d", s1, s2, &ed[i].w);
            if (!mp[s1]) mp[s1] = ++n;
            if (!mp[s2]) mp[s2] = ++n;
            ed[i].x = mp[s1]; ed[i].y = mp[s2];
        }
        scanf("%d", &k);
        root = 1;
        int ret = solve(kruskal());
        printf("Total miles driven: %d\n", ret);
    }
    return 0;
}

最小比率生成树(0-1规划)
在图中每个边都有两个属性 a , b a,b a,b你要做的是找到一个生成树,最小化
a n s = ∑ a [ i ] ∑ b [ i ] ans = \frac{\sum a[i]}{\sum b[i]} ans=b[i]a[i]
这时我们可以抽象出公式:
a n s = ∑ i = 1 n a i x i ∑ i − 1 n b i x i ans=\frac{\sum_{i=1}^n a_i x_i}{\sum_{i-1}^nb_ix_i} ans=i1nbixii=1naixi
其中 x i x_i xi的取值为0或者是1, 以表示我们取不取第i个边. 显然如果这时我们设一个 L L L,令:
a n s = ∑ i = 1 n a i x i ∑ i − 1 n b i x i > L ans=\frac{\sum_{i=1}^n a_i x_i}{\sum_{i-1}^nb_ix_i} > L ans=i1nbixii=1naixi>L
则有
∑ i = 1 n ( a i − L ∗ b i ) ∗ x i > 0 \sum_{i=1}^n (a_i-L*b_i)*x_i > 0 i=1n(aiLbi)xi>0
也就是说如果:
∃ { x i } ∑ i = 1 n ( a i − L ∗ b i ) ∗ x i > 0 \exists\{ x_i \} \sum_{i=1}^n (a_i-L*b_i)*x_i > 0 {xi}i=1n(aiLbi)xi>0
那么 a n s ans ans就会比 L L L,大否则,如果
∀ { x i } ∑ i = 1 n ( a i − L ∗ b i ) ∗ x i < = 0 \forall \{ x_i \} \sum_{i=1}^n (a_i-L*b_i)*x_i <= 0 {xi}i=1n(aiLbi)xi<=0
则, a n s ans ans就会比 L L L小.
显然我们可以二分 L L L, 每次设置边权为 a i − L ∗ b i a_i-L*b_i aiLbi然后跑最小生成树.

斯坦纳树
对于一个图 G = < V , E > G = <V, E> G=<V,E>,和一个点集 S ⊂ V S\subset V SV,要求你找到 T ⊂ E T\sub E TE,令 < S , T > <S,T> <S,T>联通,最小化边权总和.
这里使用状压dp实现
d p [ i ] [ j ] dp[i][j] dp[i][j] j j j为根节点,与要求联通的节点的状态为 i i i时的最小边权和,有两种转移方法:
d p [ i ] [ j ] = m i n { d p [ i ] [ j ] , d p [ l ] [ j ] + d p [ r ] [ j ] } (1) dp[i][j] =min\{dp[i][j], dp[l][j] + dp[r][j]\}\tag1 dp[i][j]=min{dp[i][j],dp[l][j]+dp[r][j]}(1)
d p [ i ] [ j ] = m i n { d p [ i ] [ j ] , d p [ k ] [ j ] + d i s [ i ] [ k ] } (2) dp[i][j] =min\{dp[i][j], dp[k][j] + dis[i][k]\}\tag2 dp[i][j]=min{dp[i][j],dp[k][j]+dis[i][k]}(2)
(1)式中, l , r l,r l,r i i i的一个划分.

/*
使用状压dp求解
这里值得注意的是::点的编号从0开始!
复杂度为(Dij):O(mlogn+n*2^a*(2^a+n))
ps.如果有负权边,最短路换成spfa
复杂度(Spfa):O(mn+n*2^a*(2^a+n))
ps
*/

//A为S点集中的最大数目
//洛谷模板题 P6192 为例
int he[N*N], ver[N*N], ne[N*N], e[N*N], tot;
int id[A];
int dp[1<<A][N];
bool vis[N];
int dis[N][N];
void add(int x, int y, int w)
{
    ver[++tot] = y;
    ne[tot] = he[x];
    e[tot] = w;
    he[x] = tot;
}
priority_queue<pair<int, int> > q;
void Dij(int st)
{
    memset(dis[st], inf, sizeof(dis[st]));
    memset(vis, 0, sizeof(vis));
    dis[st][st] = 0;
    q.push(pr(0, st));
    while(q.size())
    {
        int te = q.top().second; q.pop();
        if (vis[te]) continue;
        vis[te] = 1;
        for (int i = he[te]; i; i = ne[i])
        {
            int y = ver[i];
            if (dis[st][y] > dis[st][te] + e[i])
            {
                dis[st][y] = dis[st][te] + e[i];
                q.push(pr(-dis[st][y], y));
            }
        }
    }
}
int steiner(int n, int m)
{
    int top = 1 << m;
    for (int i = 0; i < n; i++) Dij(i);
    
    for (int i = 0; i < top; i++)
        for (int j = 0; j < n; j++) dp[i][j] = inf;
    for (int i = 0; i < m; i++)
        for (int j = 0; j < n; j++) dp[1<<i][j] = dis[id[i]][j];
    for (int i = 1; i < top; i++)
    {
        if ( (i & (i-1) ) == 0) continue;
        for (int k = 0; k < n; k++)
            for (int j = (i-1) & i; j > 0; j = (j - 1) & i)
                dp[i][k] = min(dp[i][k], dp[j][k] + dp[i^j][k]);
        for (int j = 0; j < n; j++)
            for (int k = 0; k < n; k++)
                dp[i][j] = min(dp[i][j], dp[i][k] + dis[k][j]);
    }
    int ret = inf;
    for (int i = 0; i < n; i++) ret = min(ret, dp[top-1][i]);
    return ret;
}
int main()
{
    int n, m, k; cin >> n >> m >> k;
    while(m--)
    {
        int x, y, w; scanf("%d%d%d", &x, &y, &w);
        add(x-1, y-1, w); add(y-1, x-1, w);
    }
    for (int i = 0; i < k; i++) scanf("%d", &id[i]), id[i]--;
    printf("%d\n", steiner(n, k));
}

不严格次小生成树
不严格的次小生成树比较好维护,使用暴力或者倍增维护两点之间最小值,枚举所有非树边加入破环即可。

/*
非严格最小生成树,
暴力枚举每个非树边,尝试加入生成树,然后在将生成的环中最大的边删去. 

模板中使用的暴力并查集
在维护并查集的时候, 要维护一个vector记录当今(x,y)的最小边. 必要
时可以使用倍增维护两点之间最小值. 

*/

// OpenJ_Bailian - 1679
const int N = 1000 + 10, M = 1000 * 1000 / 2 + 5;
const int inf = 0x3f3f3f3f;
int n, m;
int pre[N], fa[N], mx[N][N];
struct Node
{
	int x, y, w;
} ed[M];
bool vis[M];
vector<int> g[N];
bool cmp(const Node & a, const Node & b)
{
	return a.w < b.w;
}
void init()
{
	for (int i = 0; i <= n; i++)
	{
		g[i].clear();
		g[i].push_back(i);
		fa[i] = i;
	}
}
int fi(int x)
{
	if (fa[x] == x) return x;
	return fa[x] = fi(fa[x]);
}
int get_MST()
{
	sort(ed+1, ed + m + 1, cmp);
	init();
	int ans = 0, cnt = 0;
	for (int i = 1; i <= m; i++)
	{
		if (cnt == n - 1) break;
		int _x = fi(ed[i].x), _y = fi(ed[i].y);
		if (_x != _y)
		{
			cnt++;
			vis[i] = 1;
			ans += ed[i].w;
			int szx = g[_x].size(), szy = g[_y].size();
            // 维护两点之间最小的边权
			for (int j = 0; j < szx; j++)
				for (int k = 0; k < szy; k++)
					mx[g[_x][j]][g[_y][k]] = mx[g[_y][k]][g[_x][j]] = ed[i].w;
			fa[_x] = _y;
			for (int j = 0; j < szx; j++)
				g[_y].push_back(g[_x][j]);
		}
	}
	return ans;
}
void get_se_MST(int res)
{
	int ans = inf;
	for (int i = 1; i <= m; i++) if (vis[i] == 0)
		ans = min(ans, res + ed[i].w - mx[ed[i].x][ed[i].y]);

    
	if (ans > res) printf("%d\n", res);
	else puts("Not Unique!");
}
int main()
{
	int t;
	cin >> t;
	while(t--)
	{
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= m; i++)
		{
			scanf("%d%d%d", &ed[i].x, &ed[i].y, &ed[i].w);
			vis[i] = 0;
		}
	 	get_se_MST(get_MST());
	}
	return 0;
}

严格次小生成树
边权总和严格大于最小生成树的次小值

/*
严格次小生成树
相比于不严格的次小生成树,这个次小生成树的大小一定大于最小生成树
在非严格次小生成树的基础上想:是什么导致了次小生成树的"不严格"这
是因为有些去除的边和加入的比一样. 所有我们可以多维护一个严格次小
边. 破环是去除小于加边最大的. 
这里使用树上倍增维护最大边

ps.这个题洛谷时间是卡的真的死
*/

//洛谷 P4180
#pragma GCC optimize(2)

const int N = 4e5+5;
const int M  = 1e6+5;
const ll inf = 2147483647000000ll;
struct Node
{
    int u, v;
    ll w;
    int ne;
}eg[M<<1];
int tot = 0;
int he[N];
void add(int u, int v, ll w)
{
    eg[++tot].u = u; eg[tot].v = v;
    eg[tot].w = w; eg[tot].ne = he[u];he[u] = tot;
    eg[++tot].u = v; eg[tot].v = u;
    eg[tot].w = w; eg[tot].ne = he[v];he[v] = tot;
}
int f[N][24];
int mx[N][24];
int mi[N][24];
int dep[N];
void dfs(int u, int fa)
{
    f[u][0] = fa;
    for (int i = he[u]; i; i = eg[i].ne)
    {
        int v = eg[i].v;
        if (v == fa) continue;
        dep[v] = dep[u] + 1;
        mx[v][0] = eg[i].w;
        mi[v][0] = -1e9+5;
        dfs(v,u);
    }
}
int n;
void cal()
{
    for (int i = 1; i <= 20; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            f[j][i] = f[f[j][i-1]][i-1];
            mx[j][i]=max(mx[j][i-1],mx[f[j][i-1]][i-1]);
            mi[j][i]=max(mi[j][i-1],mi[f[j][i-1]][i-1]);
            if(mx[j][i-1]>mx[f[j][i-1]][i-1])mi[j][i]=max(mi[j][i],mx[f[j][i-1]][i-1]);
            else if(mx[j][i-1]<mx[f[j][i-1]][i-1])mi[j][i]=max(mi[j][i],mx[j][i-1]);
        }
    }
}
int lca(int x, int y)
{
    if (dep[x] > dep[y]) swap(x, y);
    for (int i = 20; i >= 0; i--)
    {
        if (dep[f[y][i]] < dep[x]) continue;
        y = f[y][i];
    }
    if (x == y) return x;
    for (int i = 20; i >= 0; i--)
        if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
    return f[x][0];
}
int qmax(int u, int v, ll maxx)
{
    int ans = -1e9+5;
    for (int i =20; i >= 0; i--)
    {
        if (dep[f[u][i]] >= dep[v])
        {
            if (maxx != mx[u][i]) ans = _max(ans, mx[u][i]);
            else ans = _max(ans, mi[u][i]);
            u = f[u][i];
        }
    }
    return ans;
}
Node A[M<<1];
int m;
bool cmp(Node x, Node y)
{
    return x.w < y.w;
}
int fa[N];
int fi(int x)
{
    if (x == fa[x]) return x;
    return fa[x] = fi(fa[x]);
}
bool vis[M<<1];
ll get_MST()
{
    ll cnt = 0;
    for (int i = 1; i <= m; i++)
    {
        int _u = fi(A[i].u);
        int _v = fi(A[i].v);
        if (_u == _v) continue;
        cnt += A[i].w;
        fa[_u] = _v;
        add(A[i].u, A[i].v, A[i].w);
        vis[i] = 1;
    }
    return cnt;
}
void init_mx()
{
    mi[1][0] = -1e9+5;
    dep[1] = 1;
    dfs(1, -1);
    cal();
}
ll get_sse_MST(ll cnt)
{
     ll ans = inf;
    for (int i = 1; i <= m; i++)
    {
        if (!vis[i])
        {
            int u = A[i].u, v = A[i].v;
            ll w = A[i].w;
            int _lca = lca(u, v);
            ans = min(ans, cnt - max(qmax(u, _lca, w), qmax(v, _lca, w)) + w);
        }
    }
    return ans;
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++)
        scanf("%d%d%lld", &A[i].u, &A[i].v, &A[i].w);
    sort(A+1, A+m+1, cmp);
    for (int i = 1; i <= n; i++) fa[i] = i;
    ll cnt = get_MST();
    init_mx();
    ll ans = get_sse_MST(cnt);
    printf("%lld\n", ans);
    return 0;
}

生成树计数问题
要求统计生成树的个数.这个值为其Kirchhoff 矩阵的n-1阶主子式的行列式的值。

/*
Matrix-Tree定理: 
一个图有Kirchhoff矩阵, 在这个矩阵中
如果(i == j) 则M[i][i]为i的度数
如果(i != j) 则如果(i, j) 有边则M[i][j]为-1, 否则为零. 
该图的生成树个数式这个矩阵的n-1阶主子式的行列式的值. 

ps.点从1开始编号, 图不可以有重边和自环.
*/


// 未验证模板
const int N = 128;
const double eps = 1e-9;
double K[N][N];
int n, m;
ll gauss(int n)
{ // 高消主过程
	ll res = 1;
	for (int i = 1; i <= n - 1; i++)
	{
		for (int j = i + 1; j <= n-1; j++)
		{
			while(K[j][i])
			{
				int t = K[i][i] / K[j][i];
				for (int k = i; k <= n - 1; k++)
					K[i][k] = K[i][k] - t * K[j][k];
				swap(K[i], K[j]);
				res = -res;
			}
		}
		res = res * K[i][i];
	}
	return res + eps;
}
void init()
{
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= n; j++)
			K[i][j] = 0;
}
int main()
{
	int t; cin >> t;
	
	while(t--)
	{
		init();
		scanf("%d%d", &n, &m);
		while(m--)
		{
			int x, y; scanf("%d%d", &x, &y);
			K[x][x]++; K[y][y]++;
			K[x][y]--; K[y][x]--;
		}
		printf("%d\n", gauss(n));
	}
	return 0;
}

最小生成树计数
统计一个图中最小生成树的个数

/*
Kruskal+Matrix_Tree定理求最小生成树. 
Kruskal的思想是贪心的, 所以面对一堆权值一样的边, 他会随机的挑选一个连边.
而正是在这产生了多种选择, 导致最小生成树是多样的. 那么, 我们在选择边的时候
按照边的权值大小分成几个阶段. 因为每个阶段选择的边数目一样, 最后度过这个
阶段后, 无论怎么选, 图的联通性都是一样. 所以说每个阶段的选取是互相独立的.
我们每个阶段执行:
1. 按照Kruskal贪心选边.
2. 最后形成的多个联通块, 每一个用Matrix_Tree定理统计生成树个数.累乘答案.
3. 缩点, 将联通块缩成一个点(因为接下来的阶段不会在选里面的边了)
4. 进行下一阶段

ps.点从1开始编号, 不可以有重边和自环
*/

//洛谷 P4208
const int N = 138;
const int M = 1024;
const int mod = 31011;
struct Node
{
	int x, y, w;
} ed[M];
bool cmp(const Node & a, const Node & b)
{
	return a.w < b.w;
}
int n, m;
ll f[N], u[N], vis[N]; // f[] u[], 都是并查集, f[]是缩点的并查集, 每次把缩得点合并在一起. u[]是每个阶段Kruskal用的并查集
ll g[N][N], c[N][N]; // g[][] 图的连边信息, 每连一个边更新一下. c[][]高消矩阵
vector<int> v[N]; // 联通块.

int fi(int x)
{
	if(x == u[x]) return x;
	return u[x] = fi(u[x]);
}
int fi_id(int x)
{
	if (x == f[x]) return x;
	return f[x] = fi_id(f[x]);
}
ll gauss(ll a[][N], int n)
{ // 高消主过程
	for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++)
			a[i][j] %= mod;
	int res = 1;
	for (int i = 1; i < n; i++)
	{
		for (int j = i + 1; j < n; j++)
			while(a[j][i])
			{
				int t = a[i][i] / a[j][i];
				for (int k = i; k < n; k++)
					a[i][k] = (a[i][k] - a[j][k] * t) % mod;
				for (int k = i; k < n; k++)
					swap(a[i][k], a[j][k]);
				res = -res;
			}
		if (a[i][i] == 0)
			return 0;
		res = res * a[i][i] % mod;
	}
	return (res + mod) % mod;
}
ll count_MST()
{
	sort(ed+1, ed+m+1, cmp); 
	for (int i = 1; i <= n; i++)
	{
		f[i] = i;  vis[i] = 0;
	}
	ll ww = -1; // 当前阶段的边权值
	ll ans = 1;
	for (int k = 1; k <= m+1; k++)
	{ // 注意 :: 是到m+1
		if (ed[k].w != ww || k == m+1)
		{ // 如果开始下一阶段
			for (int i = 1; i <= n; i++)
			{ // 初始化各个联通块
				if (!vis[i]) continue;
				ll _i = fi(i);
				v[_i].push_back(i);
				vis[i] = 0;
			}
			for (int i = 1; i <= n; i++)
			{ // 枚举每个联通块
				if (v[i].size() <= 1) continue;
				for (int x = 1; x <= n; x++)
					for (int y = 1; y <= n; y++)
						c[x][y] = 0; // 初始化
				int sz = v[i].size();
				for (int x = 0; x < sz; x++)
					for (int y = x+1; y < sz; y++)
					{ // 枚举块内两个端点, 初始化c
						int & xx = v[i][x];
						int & yy = v[i][y];
						c[x][y] = c[y][x] = -g[xx][yy];
						c[x][x] += g[xx][yy];
						c[y][y] += g[xx][yy];
					}
				ll res = gauss(c, sz); // 高消
				ans = (ans * res) % mod;
				for (int j = 0; j < sz; j++)
					f[v[i][j]] = i; // 缩点. 
			}
			for (int i = 1; i <= n; i++)
			{ 
				u[i] = f[i] = fi_id(i);
                //点被缩了, 除了该f[] 还要改u. 
				v[i].clear(); // 清空v.
			}
			if (k == m) break;
			ww = ed[k].w;
		}
        //尝试连接第k个边
		int x = ed[k].x;
		int y = ed[k].y;
        // 找到缩点的根节点
		x = fi_id(x), y = fi_id(y);
		if (x == y) continue; //已经缩为一点

        // 连边更新
		vis[x] = vis[y] = 1;
		u[fi(x)] = fi(y);
		g[x][y]++; g[y][x]++;
	}

    // 图不连通 答案是0
	int flag = 0;
	for (int i = 2; i <= n && !flag; i++)
		if (u[i] != u[i-1])
			flag = 1;
	if (m == 0)
		flag = 1; 
	return flag ? 0 : ans % mod;
}
int main()
{
	while(scanf("%d%d", &n, &m) != EOF)
	{
		memset(g, 0, sizeof(g));
		for (int i = 1; i <= n; i++)
			v[i].clear();
		for (int i = 1; i <= m; i++)
			scanf("%d%d%d", &ed[i].x, &ed[i].y, &ed[i].w);
		printf("%lld\n", count_MST());
	}
	return 0;
}

最小生成树计数(有自环和重边)
网传的bzoj1547板子,但是bzoj现在进不去了,没法验证。尝试在洛谷交没有重边的板子,但是wa了4个点,不清楚。也没有博客详细的讲解这个板子。板子非常短,甚至比没有重边的短。而且效率貌似还比较高??我的天,但是他没有过洛谷板子啊。。。先放在这,回头研究一下。

#include <bits/stdc++.h>
using namespace std;
struct edge
{
    int u, v, w, x;
    inline bool operator< (const edge &rhs) const
    {
        return x < rhs.x;
    }
}e[100005];
struct count
{
    int l, r, use;
}g[100005];
int n, m, fa[50005], siz[50005];
   
int getfa(int x)
{
    return fa[x] == x ? x : getfa(fa[x]);
}
   
void link(int u, int v)
{
    if(siz[u] > siz[v]) fa[v] = u, siz[u] += siz[v];
    else fa[u] = v, siz[v] += siz[u];
}
   
bool Kruskal()
{
    int cnt = 0, u, v;
    for(int i = 1; i <= m; ++i)
    {
        u = getfa(e[i].u), v = getfa(e[i].v);
        if(u != v)
        {
            link(u, v);
            ++g[e[i].w].use;
            if(++cnt == n - 1) return true;
        }
    }
    return false;
}
   
int DFS(int w, int i, int k)
{
    if(k == g[w].use) return 1;
    if(i > g[w].r) return 0;
    int ans = 0, u = getfa(e[i].u), v = getfa(e[i].v);
    if(u != v)
    {
        link(u, v);
        ans = DFS(w, i + 1, k + 1);
        fa[u] = u, fa[v] = v;
    }
    return ans + DFS(w, i + 1, k);
}
   
int main()
{
    int u, v, w, ans;
    cin >> n >> m;
    for(int i = 1; i <= n; ++i)
        fa[i] = i, siz[i] = 1;
    for(int i = 1; i <= m; ++i)
    {
        cin >> u >> v >> w;
        e[i] = (edge){u, v, 0, w};
    }
    sort(e + 1, e + m + 1);
    w = 0;
    for(int i = 1; i <= m; ++i)
        if(e[i].x == e[i - 1].x) e[i].w = w;
        else
        {
            g[w].r = i - 1;
            e[i].w = ++w;
            g[w].l = i;
        }
    g[w].r = m;
    ans = Kruskal();
    if(ans == 0) { puts("0"); return 0; }
    for(int i = 1; i <= n; ++i)
        fa[i] = i, siz[i] = 1;
    for(int i = 1; i <= w; ++i)
    {
        ans = ans * DFS(i, g[i].l, 0) % 1000003;
        for(int j = g[i].l; j <= g[i].r; ++j)
        {
            u = getfa(e[j].u), v = getfa(e[j].v);
            if(u != v) link(u, v);
        }
    }
    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值