基本的最小生成樹的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規劃)
在圖中每個邊都有兩個屬性你要做的是找到一個生成樹,最小化
這時我們可以抽象出公式:
其中的取值爲0或者是1, 以表示我們取不取第i個邊. 顯然如果這時我們設一個,令:
則有
也就是說如果:
那麼就會比,大否則,如果
則,就會比小.
顯然我們可以二分, 每次設置邊權爲然後跑最小生成樹.
斯坦納樹
對於一個圖,和一個點集,要求你找到,令聯通,最小化邊權總和.
這裏使用狀壓dp實現
以爲根節點,與要求聯通的節點的狀態爲時的最小邊權和,有兩種轉移方法:
(1)式中,爲的一個劃分.
/*
使用狀壓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;
}