生成樹相關模板

基本的最小生成樹的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,ba,b你要做的是找到一個生成樹,最小化
ans=a[i]b[i]ans = \frac{\sum a[i]}{\sum b[i]}
這時我們可以抽象出公式:
ans=i=1naixii1nbixians=\frac{\sum_{i=1}^n a_i x_i}{\sum_{i-1}^nb_ix_i}
其中xix_i的取值爲0或者是1, 以表示我們取不取第i個邊. 顯然如果這時我們設一個LL,令:
ans=i=1naixii1nbixi>Lans=\frac{\sum_{i=1}^n a_i x_i}{\sum_{i-1}^nb_ix_i} > L
則有
i=1n(aiLbi)xi>0\sum_{i=1}^n (a_i-L*b_i)*x_i > 0
也就是說如果:
{xi}i=1n(aiLbi)xi>0\exists\{ x_i \} \sum_{i=1}^n (a_i-L*b_i)*x_i > 0
那麼ansans就會比LL,大否則,如果
{xi}i=1n(aiLbi)xi<=0\forall \{ x_i \} \sum_{i=1}^n (a_i-L*b_i)*x_i <= 0
則,ansans就會比LL小.
顯然我們可以二分LL, 每次設置邊權爲aiLbia_i-L*b_i然後跑最小生成樹.

斯坦納樹
對於一個圖G=<V,E>G = <V, E>,和一個點集SVS\subset V,要求你找到TET\sub E,令<S,T><S,T>聯通,最小化邊權總和.
這裏使用狀壓dp實現
dp[i][j]dp[i][j]jj爲根節點,與要求聯通的節點的狀態爲ii時的最小邊權和,有兩種轉移方法:
dp[i][j]=min{dp[i][j],dp[l][j]+dp[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[k][j]+dis[i][k]}(2)dp[i][j] =min\{dp[i][j], dp[k][j] + dis[i][k]\}\tag2
(1)式中,l,rl,rii的一個劃分.

/*
使用狀壓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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章