题目链接
第一次打div1,失误太多……
题目大意
有一个无向图,图中的每个点都有类型。且有的类型可能没有点。现在选出两种类型,保证这两种类型的所有的点组成的子图中不存在奇数环
分析
首先是类型的个数,根据题意,最多可能可以选出 k ∗ ( k − 1 ) / 2 = 124 , 999 , 750 , 000 k * (k - 1) / 2 = 124,999,750,000 k∗(k−1)/2=124,999,750,000 种可能。即使能O(1)判环也不太行。
所以反过来,找哪些组合会出现奇数环,然后拿组合数减去可能的组合,即可得到答案。
但是仍然不能枚举每种可能。
再阅读题面发现,这是一个很稀疏的图,边数只有 500000 500000 500000 条边,也就是说,最多只有 500000 500000 500000 种组合会出现有边连接的情况。
那么我们可以枚举这些边,然后通过染色来解决奇数环问题。
但是枚举边仍然不是一个很好的办法,因为有些边左右两端的类型相同,也有不少边,他们的关系组合也是相同的,如果枚举边容易出现错解的情况,同时效率较低。
我选择使用hash的办法,讲所有可能的组合记录下来,然后把这些组合涉及到的那些边也记录下来。然后遍历所有这些组合,然后暴力染色去判奇数环。
但是可能会出现如下的数据,能成功把这个方法T掉:有一半的点属于同一组,另外一半的点分别属于不同的组,然后这些组之间都有连线。那么就会出现 n 2 \frac{n}{2} 2n的点被遍历了 n 2 \frac{n}{2} 2n次。那么复杂度仍然可以是 O ( n 2 ) O(n^2) O(n2)
继续简化,我们将属于相同类别且相邻的点放进同一个 group 中,这可以用普通的并查集实现。然后对于属于同一并查集的点,我们对其进行染黑白色。原则是相邻的点染不同的色。如果不知道染什么,则随意染色。
这时候会出现,在单一类别中,也会出现奇数环的情况,这时候需要标记一下这些类别即可,也不需要继续对这种类别继续染色。
那么我们可以将这些 group 认为是一个新的点。
接下来还是继续遍历上面 hash 得到的边
那么问题就变成了,两侧的 group 已经分别染色完成了,但是在逻辑上,任意一侧的 group 的颜色可以同时翻转,这就取决于这两个 group 的连接处的染色是否相同。那么我们可以将 group 分类为两种:反转和不反转,也就是种类并查集。
那么再用种类并查集判断是否能通过翻转颜色得到合法的子图。
注意上面本身会出现奇数环的类别,可以直接跳过这种组合,然后在最后将其删除。
AC code
#include <bits/stdc++.h>
using namespace std;
void solve() {
long long n, m, k;
cin >> n >> m >> k;
vector<vector<int>> edge(n + 1);
vector<int> type(n + 1), color(n + 1, -1), f(n + 1);
vector<bool> fail(k + 1, false);
vector<vector<pair<int, int>>> pa;
map<pair<int, int>, int> connectHash;
for (int i = 0; i < f.size(); ++i) f[i] = i;
function<int(int)> finds = [&](int x) {
return x == f[x] ? x : f[x] = finds(f[x]); };
for (int i = 0; i < n; ++i) cin >> type[i + 1];
for (int i = 0; i < m; ++i) {
int u, v;
cin >> u >> v;
edge[u].push_back(v);
edge[v].push_back(u);
int tu = type[u], tv = type[v];
if (tu > tv) swap(tu, tv);
if (tu == tv) {
int ru = finds(u), rv = finds(v);
if (ru == rv) continue;
f[ru] = rv;
continue;
}
auto iter = connectHash.find({
tu, tv});
int pos = 0;
if (iter == connectHash.end()) {
pos = connectHash.size();
connectHash.insert({
{
tu, tv}, connectHash.size()});
pa.emplace_back();
} else pos = iter->second;
pa[pos].push_back({
u, v});
}
for (int i = 1; i <= n; ++i) {
if (color[i] != -1) continue;
if (fail[type[i]]) continue;
queue<int> q;
q.push(i);
color[i] = 0;
while (!q.empty()) {
int cur = q.front();
q.pop();
int cc = color[cur];
for (auto &to : edge[cur]) {
if (type[to] != type[cur]) continue;
if (color[to] == -1) {
color[to] = cc ^ 1;
q.push(to);
} else if (color[to] == cc) {
fail[type[i]] = true;
break;
}
}
}
}
long long cnt = 0;
for (auto &item : pa) {
if (fail[type[item[0].first]]) continue;
if (fail[type[item[0].second]]) continue;
map<int, int> flag;
for (auto &p : item) {
int ru = finds(p.first), rv = finds(p.second);
if (flag.count(ru) == 0) {
flag.insert({
ru, ru});
flag.insert({
ru + n, ru + n});
}
if (flag.count(rv) == 0) {
flag.insert({
rv, rv});
flag.insert({
rv + n, rv + n});
}
function<int(int)> find2 = [&](int x) {
auto iter = flag.find(x);
if (iter == flag.end()) return x;
if (iter->second == x) return x;
iter->second = find2(iter->second);
return iter->second;
};
int ru1 = find2(ru), rv1 = find2(rv), ru2 = find2(ru + n), rv2 = find2(rv + n);
if (color[p.first] == color[p.second]) {
if (ru1 == rv1 || ru2 == rv2) {
cnt++;
break;
}
flag[ru1] = rv2;
flag[ru2] = rv1;
} else {
if (ru1 == rv2 || ru2 == rv1) {
cnt++;
break;
}
flag[ru1] = rv1;
flag[ru2] = rv2;
}
}
}
int failCnt = 0;
for (auto flag : fail) {
if (flag) {
cnt += k - 1 - failCnt;
failCnt++;
}
}
cout << k * (k - 1) / 2 - cnt << endl;
}
signed main() {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
#ifdef ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
signed localTestCount = 1, localReadPos = cin.tellg();
char localTryReadChar;
do {
if (localTestCount > 20)
throw runtime_error("Check the stdin!!!");
auto startClockForDebug = clock();
solve();
auto endClockForDebug = clock();
cout << "Test " << localTestCount << " successful" << endl;
cerr << "Test " << localTestCount++ << " Run Time: "
<< double(endClockForDebug - startClockForDebug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
} while (localReadPos != cin.tellg() && cin >> localTryReadChar && localTryReadChar != '$' &&
cin.putback(localTryReadChar));
#else
solve();
#endif
return 0;
}