1. 程式人生 > 其它 >luogu P3452 [POI2007]BIU-Offices

luogu P3452 [POI2007]BIU-Offices

兩種寫法,主要是複雜度的證明上比較有趣
1. 並查集+BFS
對於每個點,最多隻會進入佇列一次,這部分的複雜度是O(n)
每個點最多會在 for (int i = find(1); i <= n; i = find(i + 1))這段話中被訪問 \(edge[i].size() + 1\) 次,因為如果某個點和它沒有邊,這個點就會和後面的合併,再也不會在這個迴圈中被訪問到,而和它有邊的點只有 edge[i].size() 個,所以在edge[i].size() + 1 次時一定可以把它合併,因此 \(\Sigma{edge[i].size()}=m\),這部分最多被訪問 \(O(m)\)

次,因此總的複雜度為 \(O(n + m)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
vector < int > edge[N];
int ans[N], f[N], n, m, cnt, vis[N];
int find(int x) {
	return f[x] == x ? x : f[x] = find(f[x]);
}
void BFS(int x) {
	queue < int > q;
	q.push(x); f[x] = x + 1;
	ans[++cnt] = 1;
	while (!q.empty()) {
		int u = q.front(); q.pop();
		for (auto v:edge[u])
			vis[v] = u;
		for (int i = find(1); i <= n; i = find(i + 1))
			if (vis[i] != u)
				q.push(i), ans[cnt]++;
	}     
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++) {
		int a, b;
			scanf("%d%d", &a, &b);
			edge[a].push_back(b);
			edge[b].push_back(a);
		}
	for (int i = 1; i <= n + 1; i++)	f[i] = i;
	for (int i = 1; i <= n; i++)
		if (f[i] == i)
			BFS(i);
	printf("%d\n", cnt);
	sort(ans + 1, ans + cnt + 1);
	for (int i = 1; i <= cnt; i++)
		printf("%d ", ans[i]);
	return 0;
}
  1. 連結串列+佇列
    大概的想法和上面差不多,唯一的區別就是把刪除的過程變成了並查集的合併變成了連結串列的刪除。