avatar
fireworks99
keep hungry keep foolish

HDU 4408 Minimun Spanning Tree (Count)

Description

最小生成树 + 生成树计数

Analyze

相对于普通的最小生成树,多了一个条件:最小,也就是说生成树的边有了权值。如果想办法去掉权值,问题就转为了普通生成树计数了。就像三维扫描线求体积那题,遍历Z轴,如同去掉了一个维度,将问题转化为了普通的扫描线求面积。这一题能不能通过遍历边的长度而将问题转化呢?可以的。

在Kruskal算法中:假设图中有a[1]条长度为b[1]的边,有a[2]条长度为b[2]的边…a[k]条长度为b[k]的边。这a[k]条长度相同的边的排列顺序可以是任意的,这意味着顺序不影响结果(即长度为b[k]的边被遍历过后形成的连通块是相同的,即缩点后的图是相同的),也就是说在长度为b[k]的边的集合里,有num[k]个子集,选任意一个子集得到的结果都是相同的。这样一来,最终答案就是num[1] * num[2] *...*num[last]

如何求num[k] ?

将长度小于b[k]的边都处理完(并查集unite)后形成一个个集合,这一个个集合相当于新图中的一个个“点”。这a[k]条长度为b[k]的边会使这些“点”形成一个个新的连通块,将每一个连通块看做一个图,Gauss计算生成树个数,每个连通块的生成树个数累乘就是num[k]。

Something

1.并查集需要用到一个记录祖先节点的数组,这里需要用到两个这样的数组F[]、K[],K是F的将来,F是K的过去。在主函数里有如下代码:

u = found(F, e[i].u), v = found(F, e[i].v);
if(u != v)
{
    vis[u] = vis[v] = 1;
    K[ found(K, u) ] = found(K, v);
    A[u][v]++, A[v][u]++;
}

MSTC.png

按照两个数组来,上面的环是会形成的,就可以正常进行生成树计数了。倘若只有一个数组,不可能形成环,若无环生成树个数只能是1。便是这里用到了两个数组,因为图的更新需要有“时差”。在此刻之前,K和F要同步。在此刻产生时差。在其他时刻,K和F没有合作关系。

Code

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 105;
const int M = 1005;

ll ans, n, m, p;
ll A[N][N], C[N][N], F[N], K[N];
bool vis[N];
vector<ll> vec[N];

struct edge
{
    ll u, v, w;
    edge(ll a, ll b, ll c):u(a),v(b), w(c) {}
    edge() {}
    bool operator < (edge & t)const
    {
        return w < t.w;
    }
} e[M];

ll found(ll pre[], ll x)
{
    return pre[x] == x ? x : found(pre, pre[x]);
}

ll Gauss(ll cnt)
{
    ll res = 1;
    for(ll i = 0; i < cnt; ++i)
        for(ll j = 0; j < cnt; ++j)
            C[i][j] %= p;
    for(ll i = 0; i < cnt; ++i)
    {
        for(ll j = i + 1; j < cnt; ++j)
        {
            while(C[j][i])
            {
                ll t = C[i][i] / C[j][i];
                for(ll k = i; k < cnt; ++k)
                    C[i][k] = (C[i][k] - C[j][k] * t) % p;
                swap(C[i], C[j]);
                res = -res;
            }
        }
        if(C[i][i] == 0)
            return 0L;
        res = (res * C[i][i]) % p;
    }
    return (res + p) % p;
}

void Matrix_Tree()
{
    for(ll i = 0; i < n; ++i)
        if(vis[i])
            vec[ found(K, i) ].push_back(i), vis[i] = 0;

    for(ll i = 0; i < n; ++i)
        if(vec[i].size() > 1)
        {
            memset(C, 0, sizeof(C));
            ll len = vec[i].size();
            for(ll j = 0; j < len; ++j)
                for(ll k = j + 1; k < len; ++k)
                {
                    ll u = vec[i][j], v = vec[i][k];
                    if(A[u][v])
                    {
                        C[j][k] = C[k][j] -= A[u][v];
                        C[j][j] += A[u][v], C[k][k] += A[u][v];
                    }
                }

            ans = ans * Gauss(len - 1) % p;
            for(ll j = 0; j < len; ++j)
                F[ vec[i][j] ] = i;///Can't initial F[]/K[] with -1.
        }

    for(ll i = 0; i < n; ++i)
        vec[i].clear(), K[i] = F[i] = found(F, i);
}

int main()
{
    while(~scanf("%lld %lld %lld", &n, &m, &p) && n)
    {
        ll u, v, w;
        for(ll i = 0; i < m; ++i)
        {
            scanf("%lld %lld %lld", &u, &v, &w);
            u--, v--;
            e[i] = edge(u, v, w);
        }
        sort(e, e + m);

        ans = 1;
        memset(A, 0, sizeof(A));
        for(ll i = 0; i < n; ++i)
            K[i] = F[i] = i;
        for(ll i = 0; i <= m; ++i)
        {
            if(i && e[i].w != e[i - 1].w || i == m)
                Matrix_Tree();

            u = found(F, e[i].u), v = found(F, e[i].v);
            if(u != v)
            {
                vis[u] = vis[v] = 1;
                K[ found(K, u) ] = found(K, v);
                A[u][v]++, A[v][u]++;
            }
        }

        bool flag = 1;
        for(ll i = 1; i < n; ++i)
            if(F[i] != F[i - 1])
            {
                flag = 0;
                break;
            }

        printf("%lld\n", flag ? ans % p : 0);
    }
    return 0;
}

有人说路径压缩会影响点的度数什么的,进而影响邻接矩阵的正确性,故不能使用路径压缩,我想不明白,不过我试了一下路径压缩,也能AC。

还是不理解的地方

            for(ll j = 0; j < len; ++j)
                F[ vec[i][j] ] = i;///Can't initial F[]/K[] with -1.
        }

    for(ll i = 0; i < n; ++i)
        vec[i].clear(), K[i] = F[i] = found(F, i);
}

Matrix_Tree()函数里后一部分,这里(两个循环)是在更新,可是K不就是新图吗?把K拷贝给F不行吗?我试了不行。所以这算法我还有一个点没想明白,一个很大的、很重要的点!

Site by Baole Zhao | Powered by Hexo | theme PreciousJoy