UVa1265/LA4848 Tour Belt
- 题目链接
- 题意
- 分析
- AC 代码
题目链接
本题是2010年icpc亚洲区域赛大田赛区的F题
题意
给出一个有n个结点m条边的加权无向图G(2≤n≤5000,1≤m≤n(n-1)/2),满足如下条件的结点集B(2≤|B|≤n)称为候选子图。B内的结点在G中连通,且B的任何一条内弧的权值严格大于任何一条边界弧的权值。这里的内弧是指两个端点都在B中的弧,边界弧是指恰好有一个端点在B中的弧。你的任务是求出所有候选子图的结点数之和。
如下图所示,左图有3个候选子图{1,2},{3,4}和{1,2,3,4},结点数之和为8。右图有6个候选子图,分别为{1,2},{3,4},{5,6},{7,8},{3,4,5,6}和{1,2,3,4,5,6,7,8},结点数之和为20。注意{1,2,7,8}不是候选子图,因为这些结点在G中不连通({1,2}和{7,8}之间没有边相连)。
分析
首先可以想到如果最大权边唯一,那么其两端点构成的子图是候选子图,否则要看沿两端点延伸的其他边对应的端点是否要包含到候选子图中,找到初始候选子图容易想到将其缩成一点继续迭代,不过这样在本题的数据规模下会超时。
仔细分析以上缩点迭代的过程,其实就是边权从大到小排序后求最大生成树(也可以是森林)的过程,当前边合并进来后,检查其所在的连通分量是否满足候选子图的要求然后更新答案计数即可。不过这种做法其实复杂度为 O ( m n ) O(mn) O(mn),感觉数据刁钻一点的话也会超时,没想到提交AC了并且才几十毫秒。
AC 代码
#include <iostream>
#include <algorithm>
using namespace std;
#define N 5002
struct edge {
int u, v, w;
bool operator< (const edge &rhs) const {
return w > rhs.w;
}
} e[N*N>>1];
struct {int v, w;} g[N][N]; int f[N], c[N], cc[N], m, n;
int find(int x) {
return x == f[x] ? x : f[x] = find(f[x]);
}
bool check(int r) {
int x = 0, y = 200000;
for (int u=1; u<=n; ++u) if (find(u) == r) for (int i=0; i<c[u]; ++i) {
int v = g[u][i].v, w = g[u][i].w;
find(v) == r ? y = min(y, w) : x = max(x, w);
}
return y > x;
}
int solve () {
cin >> n >> m;
for (int i=1; i<=n; ++i) f[i] = i, c[i] = 0, cc[i] = 1;
for (int i=0, u, v, w; i<m; ++i)
cin >> u >> v >> w, g[u][c[u]++] = {v, w}, g[v][c[v]++] = {u, w}, e[i] = {u, v, w}, f[find(u)] = find(v);
sort(e, e+m);
int ans = 0, cnt = 0;
for (int i=1; i<=n; ++i) if (find(i) == i) ++cnt;
for (int i=1; i<=n; ++i) f[i] = i;
for (int i=0; i<m; ++i) {
int x = find(e[i].u), y = find(e[i].v);
if (x == y) continue;
f[x] = y; cc[y] += cc[x];
if (check(y)) ans += cc[y];
if (++cnt == n) break;
}
return ans;
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
int t; cin >> t;
while (t--) cout << solve() << endl;
}