最小生成树

最小生成树

AcWing.346 走廊泼水节

简要题意

给定一个 N 个节点的树,要求增加若干条边,把这棵树扩充为完全图,并满足图的唯一最小生成树仍然是这棵树。求增加的边的权值总和最小是多少,保证边权位非负整数。

题目分析

考虑 kruskal 的过程,是把权值从小到大排序,依次扫描每一个边。那么我们想让这棵新的树的最小生成树仍然不变且是唯一的,那么我们的边权应该设为 \(z+1\)\(z\) 是当前扫描边的边长。那么两个点之间要比原图多多少条边呢?为 \(|S_x|*|S_y|-1\)\(S_x\) 表示 \(x\) 所在的并查集。所以只需要在原来跑 kruskal 的过程上多维护一个 \(S\) 就可以了。

struct rec
{
	int x, y, z;
	friend bool operator < (rec a, rec b)
	{
		return a.z < b.z;
	}
} edge[M];

int find(int x)
{
	return fa[x] == x ? x : fa[x] = find(fa[x]);
}

int kruscal()
{
	sort(edge + 1, edge + n);
	int idx = 0; ans = 0;
	for (rint i = 1; i <= n; i++) fa[i] = i, s[i] = 1;
	for (rint i = 1; i < n; i++)
	{
		int x = find(edge[i].x);
		int y = find(edge[i].y);
		if (x == y) continue;
		fa[x] = y;
		idx++;
		ans += (edge[i].z + 1) * (s[x] * s[y] - 1);
		s[y] += s[x];
	}
	if (idx < n - 1) return inf;
	return ans;
}

signed main()
{
	int T;
	cin >> T;
	while (T--)
	{
		cin >> n;
		for (rint i = 1; i < n; i++)
		{
			cin >> edge[i].x >> edge[i].y >> edge[i].z;
		}
		cout << kruscal() << endl;	
	}
	return 0;
}

AcWing 347. 野餐规划

简要题意

给定一张 \(N\) 个点 \(M\) 条边的无向图,求出无向图的一棵最小生成树,满足 \(1\) 号节点的度数不超过给定的整数 \(S\)\(N\) 不超过 \(30\).

题目分析

首先,去掉一号节点之后,无向图可能会分成若干个联通块。可以用深度优先遍历划分出图中的每个联通块。设联通块共有 \(T\) 个,若 \(T > S\),则本题无解。

对于每个联通块,在这个联通块内部求出它的最小生成树,然后从联通块中选出一个节点 \(p\)\(1\) 号节点相连,其中无向边 \((1,p)\) 的权值尽量小。

此时,我们已经得到了原无向图的一棵生成树,\(1\) 号节点的度数为 \(T\)。我们还可以尝试改动 \(S-T\),让答案更优。

考虑无向图中从节点 \(1\) 出发的每条边 \((1,x)\) ,边权为 \(z\)。如果 \((1,x)\) 还不在当前的生成树中,那么继续找到当前生成树中从 \(x\)\(1\) 的路径上权值最大的边 \((u,v)\),边权为 \(w\)。求出使得 \(w-z\) 最大的点 \(x_0\)。若 \(x_0\) 对应的 \(w_0-z_0 > 0\),则从树中删掉边 \((u_0,v_0)\),加入边 \((1,x_0)\),答案就会变小 \(w_0-z_0\)

重复上一步 \(S - T\) 或者直到 \(w_0-z_0<=0\),就得到了题目所求的最小生成树。

int n, s;
int tot;
int g[N][N];
int fa[N];
int block[N], cntb;
map<pair<int, int>, bool> v;
map<string, int> mp;
struct rec 
{
	int x, y, z;
    friend bool operator < (rec a, rec b)
	{
		return a.z < b.z;
	}
} edge[N];
struct node 
{
	int a, b;
	int dist;
} f[N];

int find(int x) 
{
	if (fa[x] != x) fa[x] = find(fa[x]);
	return fa[x];
}

int kruskal() 
{
	sort(edge + 1, edge + 1 + n);
	for (rint i = 1; i <= tot; i++) fa[i] = i;
	int ans = 0;
	for (rint i = 1; i <= n; i++) 
	{
		int x = find(edge[i].x);
		int y = find(edge[i].y);
		if (x == 1 || y == 1 || x == y) continue;
		fa[x] = y;
		v[{edge[i].x, edge[i].y}] = 1;
		v[{edge[i].y, edge[i].x}] = 1; 
		ans += edge[i].z;
	}
	return ans;
}

void dfs(int x) 
{
	for (rint y = 2; y <= tot; y++) 
	{
		if (!g[x][y] || block[y]) continue;
		block[y] = cntb;
		dfs(y);
	}
}

void dfs(int x, int father) 
{
	for (rint y = 2; y <= tot; y++) 
	{
		if (y == father || !v[{x, y}]) continue;
		if (~f[y].dist) continue;
		if (f[x].dist > g[x][y]) f[y] = f[x];
		else f[y] = {x, y, g[x][y]};
		dfs(y, x);
	}
}

signed main() 
{
	cin >> n;
	mp["Park"] = tot = 1;
	for (rint i = 1; i <= n; i++) 
	{
		string a, b;
		int c;
		cin >> a >> b >> c;
		if (!mp[a]) mp[a] = ++tot;
		if (!mp[b]) mp[b] = ++tot;
		g[mp[a]][mp[b]] = g[mp[b]][mp[a]] = c;
		edge[i] = {mp[a], mp[b], c};
	}
	cin >> s;
	for (rint i = 2; i <= tot; i++) 
	{
		if (!block[i]) 
		{
			cntb++;
			block[i] = cntb;
			dfs(i);
		}
	}
	int sum = kruskal();
	for (rint i = 1; i <= cntb; i++) 
	{
		int minn = inf, id = 0;
		for (rint j = 2; j <= tot; j++) 
		{
			if (block[j] == i) 
			{
				if (g[1][j] && minn > g[1][j]) 
				{
					minn = g[1][j];
					id = j;
				}
			}
		}
		sum += minn;
		v[{1, id}] = 1;
		v[{id, 1}] = 1;
	}
	int t = cntb;
	while (t < s) 
	{
		s--;
		for (rint i = 0; i <= 25; i++) f[i] = {0, 0, -1};
		dfs(1, 0);
		int maxx = 0, id = 0;
		for (rint j = 2; j <= tot; j++) 
		{
			if (g[1][j] && maxx < f[j].dist - g[1][j]) 
			{
				maxx = f[j].dist - g[1][j];
				id = j;
			}
		}
		if (!maxx) break;
		v[{f[id].a, f[id].b}] = v[{f[id].b, f[id].a}] = 0;
		v[{1, id}] = v[{id, 1}] = 1;
		sum -= maxx;
	}
	cout << "Total miles driven: " << sum << endl;
	return 0;
}

AcWing348. 沙漠之王

简要题意

给这一张 \(N\) 个点 \(M\) 条边的无向图,图中每条边 \(e\) 都有一个收益 \(C_e\) 和一个成本 \(R_e\),求该图的一颗生成树 \(T\), 使树中各边的收益之和除以成本之和,即 \(∑_{e∈T}C_e/∑_{e∈T}R_e\) 最大。\((1<N,M<10000)\)

题目分析

\(x=w/l\)\(w-l*x =0,f(x) = w-l*x\);将边权更改为 \(w-l*x\) 来求生成树

因为 \(f(x)\) 是个单调递减函数,随着 \(x\) 的增大而减少,对于任意一个生成树如果 \(f(x)>0\),则 \(l\) 需要增大 \(f(x)<0\) 否则 \(l\) 需要减小 若要满足 \(f(x)==0\) 恒成立

1.若要 \(x\) 取最大值,则不能存在任意一个生成树 \(f(x)>0\), 否则 \(x\) 还能继续增大,即任意生成树 \(f(x)<=0\) 若存在一个生成树 \(f(x)>0\),则那个生成树的比率一定大于当前 \(x\), \(w/l > x\)\(w-l*x > 0\)

2.若要 \(x\) 取最小值,则不能存在任意一个生成树 \(f(x)<0\),否则 \(x\) 还能继续减小,即任意生成树 \(f(x)>=0\) 若存在一个生成树 \(f(x)<0\),则那个生成树的比率一定小于当前 \(x\), \(w/l < x\)\(w-l*x < 0\)

若要满足 \(f(x)>0\) 恒成立,则最小生成树 \(>0\)

若要满足 \(f(x)<0\) 恒成立,则最大生成树 \(<0\)

此题目求解最小的 \(x\) 值,也就是检查是否所有的生成树 \(f(x)>=0\),即最小生成树 \(>=0\)

如果最小生成树大于 \(0\),所有的生成树都满足 \(f(x)>0\), 尝试增加 \(x\) 得到 \(f(x)=0\)

否则,有生成树不满足这个条件,那么 \(x\) 一定要减少来使所有 \(f(x)>=0\)

double calc(int a, int b) 
{
	return sqrt((x[a] - x[b]) * (x[a] - x[b]) + (y[a] - y[b]) * (y[a] - y[b]));
}

bool check(double mid) 
{
    fill(dist, dist + n + 1, dinf);
    fill(v, v + n + 1, 0);
	dist[1] = 0;
	double ans = 0;
	for (rint i = 1; i <= n; i++) 
	{
		int x = 0;
		for (rint j = 1; j <= n; j++)
		{
			if (!v[j] && (x == 0 || dist[j] < dist[x])) x = j;		
		}
		v[x] = 1;
		ans += dist[x];
		for (rint y = 1; y <= n; y++) 
		{
			if (!v[y]) dist[y] = min(dist[y], fabs(w[x] - w[y]) - mid * calc(x, y));
		}
	}
	return ans >= 0;
}

signed main() 
{
	while (cin >> n && n) 
	{
		for (rint i = 1; i <= n; i++)
		{
			cin >> x[i] >> y[i] >> w[i];
		}
		double l = 0, r = 10000000;
		double ans;
		while ((r - l) > eps) 
		{
			double mid = (l + r) / 2;
			if (check(mid)) ans = mid, l = mid;
			else r = mid;
		}
		cout << fixed << setprecision(3) << ans << endl;
	}
	return 0;
}

AcWing.349. 黑暗城堡

题目大意

问你有多少棵最短路径树

题目分析

这里用的邻接矩阵 Dijkstra

对于已经是最短路的情况,对于任意两个点 \(x,y\)\(dist[y] <= dist[x] + z\)

现在考虑 \(dist[y] <= dist[x] + z\),那么在最短路径生成树中一定不能有这一条边。如果有这一条边,那么 \(y\) 的路径就不是最小的。(因为是树,所以只能是这一个点来对 \(y\) 进行更新)

那么当 \(dist[y]==dist[x]+z\),最短路径生成树里面可以包含这一条边。

1.对于每一个点,都有到达 \(1\) 号点的距离。现在按照距离从小到大对点进行考虑。

2.对于考虑到的 \(i\) 个点,查找已经遍历过的集合,看有多少 \(x\) 满足 \(dist[i]==dist[x]+z\)。这是方案数。使用乘法原理。

int n, m;
int a[N][N];
int dist[N];
int ans = 1;
bool v[N];
pair<int, int> f[N];

signed main()
{   
    cin >> n >> m;
	memset(a, 0x3f, sizeof a);
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;
    
	for (rint i = 1; i <= n; i++) a[i][i] = 0;
    for (rint i = 1; i <= m; i++)
    {
        int x, y, z;
        cin >> x >> y >> z;
        a[x][y] = a[y][x] = min(a[x][y], z);
    }
    
    for (rint i = 1; i <= n; i++)
    {
        int x = 0;
        for (rint j = 1; j <= n; j++)
            if (!v[j] && (x == 0 || dist[x] > dist[j])) x = j;
        v[x] = 1;
        for (rint y = 1; y <= n; y++)
        {
            if (!v[y]) dist[y] = min(dist[y], dist[x] + a[x][y]);   
        }
    }
    
    for (rint i = 1; i <= n; i++) f[i] = {dist[i], i};
    sort(f + 1, f + n + 1);
    
    memset(v, 0, sizeof v);
    v[1] = 1;
    for (rint i = 2; i <= n; i++)
    {
        int y = f[i].second;
        int cnt = 0;
        for (rint x = 1; x <= n; x++) 
        {
            if (v[x] && dist[x] + a[x][y] == dist[y]) cnt++;
        }
        ans = ans * cnt % mod;
        v[y] = 1;
    }
    cout << ans << endl;
    return 0;
}

AcWing.388. 四叶草魔杖

题目大意

给定一张无向图,结点和边均有权值。所有结点权值之和为 \(0\),点权可以沿边传递,传递点权的代价为边的权值。求让所有结点权值化为 \(0\) 的最小代价。

题目分析

容易想到本题与最小生成树有关。一种不难想出的思路是求出原图的最小生成树,将最小生成树上所有边的权值之和作为答案。

但经过思考,可以发现这样得到的不一定是最优解。首先,原图可能并不联通;其次,可以将原图划分为若干个点权之和均为 \(0\) 的子图,在这些子图中分别转移点权,最后将答案合并。这样得到的方案或许会更优。

此时我们发现划分方案不止一种,如何确定最终的方案成了需要解决的最大问题。

注意到本题中 \(N\) 范围较小,允许我们把所有点权和为 \(0\) 的子图(以下简称“合法子图”)的最小生成树全部求出。因此可以先枚举原图点集的所有子集,对于每个点权和为 \(0\) 的点集,用这些点和连接它们的边构造一张合法子图。我们能够轻易求出这些合法子图的最小生成树。但有些合法子图或许并不联通,为避免对之后的求解造成影响,需要把这些子图的最小生成树边权和设为 \(\infty\)

接下来需要把这些子图中的若干个合并起来,得到全局最优解。与划分的情形相同,合并这些子图的方案也有多种。可以使用 \(DP\) 得到最优解。

具体地,考虑进行类似背包的 \(DP\),将每个合法子图视作可以放入背包的一个物品。设 \(A\)\(B\) 为两个不同合法子图的点集,合法子图的最小生成树边权和为 \(S\),可以写出如下状态转移方程:

$f_{A \cup B}=min {f_{A\cup B}, f_{A}+S_{B} },A\cap B=\oslash $

最终 \(f_{2^n-1}\) 即为所求的答案。

int n, m;
int a[N], fa[N];
int s[M], f[M], p[M];

struct rec 
{
	int x, y, z;
	friend bool operator < (rec a, rec b)
	{
		return a.z < b.z;
	}
} edge[M];

int find(int x) 
{
	return fa[x] == x ? x : fa[x] = find(fa[x]);
}

int kruskal(int s) 
{
	int ans = 0;
	for (rint i = 0; i < n; i++) if (s & (1 << i)) fa[i] = i;
	for (rint i = 1; i <= m; i++) 
	{
		if (!(s & (1 << (edge[i].x))) || !(s & (1 << (edge[i].y)))) continue;
		int x = find(edge[i].x);
		int y = find(edge[i].y);
		if (x == y) continue; 
		fa[x] = y;
		ans += edge[i].z;
	}
	int father = -1;
	for (rint i = 0; i < n; i++)
	{
		if (s & (1 << i))
		{
			if (father == -1) father = find(i);
			else if (find(i) != father) return inf; 			
		}
	}
	return ans;
}

signed main() 
{
	cin >> n >> m;
	
	for (rint i = 1; i <= n; i++) cin >> a[i];
	for (rint i = 1; i <= m; i++) cin >> edge[i].x >> edge[i].y >> edge[i].z; 
		
	for (rint i = 1; i < (1 << n); i++)
		for (rint j = 0; j < n; j++)
			if (i & (1 << j))
			    s[i] += a[j + 1]; 	

	sort(edge + 1, edge + m + 1);
	for (rint i = 1; i < (1 << n); i++) 
	{
		if (!s[i]) p[i] = kruskal(i); 
		f[i] = inf;
	}
	f[0] = 0;
	for (rint i = 1; i < (1 << n); i++) 
	{ 
		if (s[i]) continue;
		for (rint j = 0; j < (1 << n); j++)
		{
			if (!(i & j)) f[i | j] = min(f[i | j], f[j] + p[i]);			
		}
	}
	if (f[(1 << n) - 1] >= inf) puts("Impossible");
	else cout << f[(1 << n) - 1] << endl;
	
	return 0;
}

P3623 免费道路

题目大意

个图边权为 \(0\)\(1\),求一个生成树使得边权和为 \(k\)

题目分析

题目中说要保留 \(k\) 条边,有一些 \(1\) 边是要必须保留的。这样才能保证图的连通性。

先以 \(0\) 边优先做一遍 kruskal,把必须要加入的 \(1\) 边个数算出。

再做一遍 kruskal,先把那些必须加的加入,然后再加 \(1\) 边使得达到 \(k\) 条,然后就一直加 \(0\) 边了。

在上述过程中,有两种无解的情况:

1.图不练通

2.必须要加的边超过 \(k\)

bool cmp1(node a, node b) { return a.z > b.z;}
bool cmp2(node a, node b) { return a.z < b.z;}

int find(int x) 
{
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}

void check() 
{
	int l = find(1);
	for (rint i = 2; i <= n; i++) 
	{
		int r = find(i);
		if (r != l)  puts("no solution"), exit(0);
		l = r;
	}
}

signed main()
{
	cin >> n >> m >> k;
	for (rint i = 1; i <= m; i++)
	{
		cin >> edge[i].x >> edge[i].y >> edge[i].z;
	}
	
	//kruscal 1
	cnt = idx = 0;
	for (rint i = 1; i <= n; i++) fa[i] = i;
	sort(edge + 1, edge + m + 1, cmp1);
	for (rint i = 1; i <= m; i++)
	{
		int x = find(edge[i].x);
		int y = find(edge[i].y);
		if (x == y) continue;	
		fa[x] = y;	
		if (!edge[i].z)
		{
			idx++; 
			edge[i].z = -1;				
		}
	}
	if (idx > k) 
	{
        puts("no solution");
		return 0;
	}
	check();
	
	//kruscal 2
	cnt = idx = 0;
	for (rint i = 1; i <= n; i++) fa[i] = i;
	sort(edge + 1, edge + m + 1, cmp2);
	for (rint i = 1; i <= m; i++) 
	{
		int x = find(edge[i].x);
		int y = find(edge[i].y);
		if (x == y)  continue;
		if (edge[i].z == 1 || idx < k) 
		{
			fa[x] = y;
			ans[++cnt] = edge[i];
			if (edge[i].z < 1) idx++, edge[i].z = 0;
		}
	}
	if (idx < k) 
	{
        puts("no solution");
		return 0;
	}
	check();
	
	for (rint i = 1; i <= cnt; i++) 
	{
		if (ans[i].z == -1)
		{
			ans[i].z = 0;
		}
		cout << ans[i].x << " " << ans[i].y << " " << ans[i].z << endl;
	}
	
	return 0;
}

CF888G Xor-MST

题目大意

给定 \(n\) 个结点的无向完全图。每个点有一个点权为 \(a_i\)。连接 \(i\) 号结点和 \(j\) 号结点的边的边权为 \(a_i\oplus a_j\)。求这个图的 MST 的权值。

题目分析

每一轮维护一个 Trie 来存储所有 \(a_i\) ,对于每个联通块,先把联通块里的数在 Trie 删掉,然后在 Trie 里查询其他点和联通块里每个点的最小异或和。

int n, m;
int a[N], L[M], R[M];
int ch[2][M], rt, cnt;

void insert(int &k, int id, int dep) 
{
	if (!k) k = ++cnt;
	if (!L[k]) L[k] = id;
	R[k] = id;
	if (dep == -1) return ;
	insert(ch[(a[id] >> dep) & 1][k], id, dep - 1);
}

int query(int k, int x, int dep) 
{
	if (dep == -1) return 0;
	int v = (x >> dep) & 1;
	if (ch[v][k]) return query(ch[v][k], x, dep - 1);
	return query(ch[v ^ 1][k], x, dep - 1) + (1 << dep);
}

int dfs(int k, int dep) 
{
	if (dep == -1) return 0;
	if (ch[0][k] && ch[1][k]) 
	{
		int ans = 1e18;
		for (rint i = L[ch[0][k]]; i <= R[ch[0][k]]; i++)
		{
			ans = min(ans, query(ch[1][k], a[i], dep - 1) + (1 << dep));
		}
		return dfs(ch[0][k], dep - 1) + dfs(ch[1][k], dep - 1) + ans;
	} 
	else if (ch[0][k]) return dfs(ch[0][k], dep - 1);
	else if (ch[1][k]) return dfs(ch[1][k], dep - 1);
	return 0;
}

signed main() 
{
	cin >> n;
	for (rint i = 1; i <= n; i++) cin >> a[i];
	sort(a + 1, a + n + 1);
	for (rint i = 1; i <= n; i++) insert(rt, i, 30);
	cout << dfs(rt, 30) << endl;
	return 0;
}

P4208 最小生成树计数

题目大意

一个简单无向加权图。求这个图中有多少个不同的最小生成树。如果两颗最小生成树中至少有一条边不同,则这两个最小生成树就是不同的。

题目分析

这个题做法很多,难的不会,就会一个简单的做法,复杂度是 \(O(2^{10}m)\) 的,足以通过此题。

显然的,每种权值的边的数量是固定的,那么我们先统计出每种权值需要多少条边,记为 \(c_i\)

发现具有相同权值的边的数量不超过 \(10\) 条,暴力枚举第 \(i\) 种权值的边选择哪 \(c_i\) 条,乘法原理统计答案。

然后我第一遍打完发现运行样例结果得出来 \(7\),因为要快速分开连通块,并查集中不能使用路径压缩。

int n, m, cnt, sum;
int l[M], r[M], c[M];
int fa[N];

struct rec
{
	int x, y, z;
	friend bool operator < (rec a, rec b)
	{
		return a.z < b.z;
	}
} edge[M];

int find(int x) 
{
	return fa[x] == x ? x : find(fa[x]);
}

void dfs(int now, int x, int num) 
{
	if (now > r[x]) 
	{
		sum += (num == c[x]);
		return ;
	}
	int X = find(edge[now].x);
	int Y = find(edge[now].y);
	if (X == Y) 
	{
		dfs(now + 1, x, num);
		return ;
	}
	fa[X] = Y;
	dfs(now + 1, x, num + 1);
	
	fa[X] = X;
	fa[Y] = Y;
	dfs(now + 1, x, num);
}

void kruscal()
{
	sort(edge + 1, edge + m + 1);
	for (rint i = 1; i <= n; i++) fa[i] = i;
	int idx = 0;
	for (rint i = 1; i <= m; i++) 
	{
		if (edge[i].z != edge[i - 1].z)
		{
			r[cnt] = i - 1;
			l[++cnt] = i;
		} 		
		
		int x = find(edge[i].x);
		int y = find(edge[i].y);
		if (x == y) continue;
		idx++;
		c[cnt]++;
		fa[x] = y;
	}
	r[cnt] = m;
	
	if (idx < n - 1) 
	{
		puts("0");
		exit(0);
	}	
}

signed main() 
{
	cin >> n >> m;
	for (rint i = 1; i <= m; i++) cin >> edge[i].x >> edge[i].y >> edge[i].z;
	
	kruscal();
	
	for (rint i = 1; i <= n; i++) fa[i] = i;
	int ans = 1;
	for (rint i = 1; i <= cnt; i++) 
	{
		sum = 0;
		dfs(l[i], i, 0);
		ans = ans * sum % mod;
		for (rint j = l[i]; j <= r[i]; j++)
		    fa[find(edge[j].x)] = find(edge[j].y);
	}
	
	cout << ans << endl;
	
	return 0;
}

UVA1395

题目大意

求所有生成树中最大边权与最小边权差最小的,输出它们的差值。

题目分析

想到暴力,求出每个生成树的边权差值,最后取个 min。

首先对边排个序,枚举 K 然后不断地记录答案就可以了。

int kruscal()
{
	ans = inf;
	memset(edge, 0, sizeof edge);
	cin >> n >> m;
	if (m == 0 && n == 0) return 0;
	for (rint i = 1; i <= m; i++) cin >> edge[i].x >> edge[i].y >> edge[i].z;
	sort(edge + 1, edge + m + 1);
	for (rint i = 1; i <= m; i++)
	{
		int idx = 0, maxx = 0;
		for (rint j = 1; j <= n; j++) fa[j] = j;
		for (rint j = i; j <= m; j++)
		{
			int x = find(edge[j].x);
			int y = find(edge[j].y);
			if (x == y) continue;
			fa[x] = y;
			idx++;
            maxx = max(maxx, edge[j].z - edge[i].z);
			if (idx == n - 1)
			{
				break;
			}
		}
		if (idx < n - 1) continue;;
		ans = min(ans, maxx); 
	}
	if (ans >= inf) cout << -1 << endl;
	else cout << ans << endl;
	return 1;
}

signed main() 
{
	while (kruscal());
	return 0;
}

CF1242B

题目大意

求出一张 01 完全图的最小生成树的权值

题目分析

数据范围很大,直接 Kruscal 不了。考虑返璞归真使用 dfs

求出所有可以用 0 边连成的联通块,那么将这些联通快连起来,就可以构造出一颗最小生成树

int n, m;
int ans;
unordered_map<int, int> g[N];
set<int> s, s1;

void dfs(int x) 
{
	vector<int> v;
	v.clear(), s1.clear();
	for (auto i = s.begin(); i != s.end(); i++)
	{
		if (!g[x][*i]) v.push_back(*i);
		else s1.insert(*i);
	}
	s = s1; 
	for (rint i : v) dfs(i);
}

signed main() 
{
	cin >> n >> m;
	
	for (rint i = 1; i <= m; i++)
	{
		int a, b;
		cin >> a >> b;
		g[a][b] = g[b][a] = 1;
	}
	
	for (rint i = 1; i <= n; i++) s.insert((int)i);
	
	while (!s.empty())
	{
		ans++;
		int t = *s.begin();
		s.erase(t);
		dfs(t);
	}
	
	cout << ans - 1 << endl;
	
	return 0;
}
posted @ 2024-03-07 20:42  PassName  阅读(20)  评论(0编辑  收藏  举报