CF1833E Round Dance

发布时间 2023-05-23 17:29:39作者: ForgotDream

赛后三分钟做出来的。

思路

对于最大值,我们可以用并查集维护连通性,将题目中给出的所有人与其知道的邻居合并起来,那么根据贪心的思想,最大的可能的值就是此时并查集中联通块的个数。

而最小值要麻烦一些。首先对于每个人 \(i\),如果他记得的邻居 \(j = a_i\) 所记得的邻居 \(k = a_j\) 不是他自己的话,那么 \(j\) 就是确定的,因为 \(j\) 的两个邻居 \(i\)\(k\) 都已知。

当一个连通块中所有人都已确定时,这个连通块有唯一确定的组成。反之,当一个联通块中存在不确定的人时,一定可以找出一种方法将其与其他的不确定的连通块合并起来,组成一个更大的联通块。

在求最小值时,我们先找出所有不确定的连通块的个数 \(cir\),再找出总的连通块个数 \(cnt\),那么不难看出最小个数就为 \(cnt - cir + 1\)

但是还有一个小坑就是,当 \(cir = 0\),即所有连通块都确定时,答案显然为 \(cnt\) 而不是 \(cnt + 1\)

代码

int n;
std::cin >> n;

std::vector<int> a(n);
DSU dsu(n);
int cir = 0;
for (int i = 0; i < n; i++) {
  std::cin >> a[i];
  a[i]--;
  dsu.merge(i, a[i]);
}

std::vector<bool> det(n);
for (int i = 0; i < n; i++) {
  if (i != a[a[i]]) {
    det[a[i]] = true;
  }
}

std::vector<bool> used(n);
for (int i = 0; i < n; i++) {
  if (!det[i]) {
    used[dsu.find(i)] = true;
  }
}

cir = std::count(used.begin(), used.end(), true);

int cnt = 0;
for (int i = 0; i < n; i++) {
  if (dsu.find(i) == i) {
    cnt++;
  }
}

std::cout << std::min(cnt, cnt - cir + 1) << " " << cnt << "\n";