基本的最小生成树的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=∑i−1nbixi∑i=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=∑i−1nbixi∑i=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=1∑n(ai−L∗bi)∗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=1∑n(ai−L∗bi)∗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=1∑n(ai−L∗bi)∗xi<=0
则,
a
n
s
ans
ans就会比
L
L
L小.
显然我们可以二分
L
L
L, 每次设置边权为
a
i
−
L
∗
b
i
a_i-L*b_i
ai−L∗bi然后跑最小生成树.
斯坦纳树
对于一个图
G
=
<
V
,
E
>
G = <V, E>
G=<V,E>,和一个点集
S
⊂
V
S\subset V
S⊂V,要求你找到
T
⊂
E
T\sub E
T⊂E,令
<
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;
}