CodeForces-Round Dance
题目

看到没人用 tarjan,于是写了篇。
题面描述
有 \(n\) 个人围成若干个圈(也有可能只有一个)跳舞,每个圈至少有 \(2\) 个人。在圈中,每个人都与 \(2\) 个人相邻。特殊地,如果圈里只有 \(2\) 个人,则实际上只与一个人相邻。
做题思路
一道很好的题目,我们可以想到只要一个联通块满足强连通分量,那就肯定可以算一个答案,所以我们可以使用 tarjan 算法去求所有的强连通分量,这就是最大值。
然后思考最小值,很明显这道题给了提示,就是两个人成链这种情况,这种成不了环但是呢,会被 tarjan 算成一个强连通分量,我们要做的就是对所有这种情况合并即可,这种两个人是成不了环的,但是合并就行,可以合并成一个环的,这样我们就可以求最小值了。
最后就是怎么判断这种两个人成链,我们可以联想到树,既然这两个人对比别的人是独立的,那他们的深度肯定是 \(1\),因为输入只出现了一次,所以可以这样计算捏。
代码
代码简洁并且配有注释,不懂的地方可以直接评论,一小时之内答复。
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int range = 2e5 + 5;
int n;
int a[range];
vector<int>e[range];
int dfn[range];
int siz[range];
int instk[range];
int stk[range];
int scc[range];
int low[range];
int top, tot, cnt;
//以上都是tarjan要用的数组
int depth[range];//拿来判断双人成链的深度数组
//模板 tarjan 可以b站搜董晓算法有讲解的
void tarjan(int x) {
dfn[x] = low[x] = ++tot;
stk[++top] = x;
instk[x] = 1;
for (auto y : e[x]) {
if (!dfn[y]) {
tarjan(y);
low[x] = min(low[x], low[y]);
} else if (instk[y]) {
low[x] = min(low[x], dfn[y]);
}
}
if (dfn[x] == low[x]) {
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;
++siz[cnt];
} while (y != x);
}//do while 无论如何都会进行一次
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
depth[i] = 0;
e[i].clear();
siz[i] = 0;
scc[i] = 0;
dfn[i] = 0;
instk[i] = 0;
stk[i] = 0;
top = cnt = tot = 0;
}
map<pair<int, int>, bool>ma;
for (int i = 1, v; i <= n; i++) {
cin >> v;
if (!ma[ {i, v}] && !ma[ {v, i}]) {
ma[ {i, v}] = ma[ {v, i}] = 1;
depth[v]++;
depth[i]++;
}//判断重复防止多加
e[i].push_back(v);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) {
tarjan(i);
}
}
int maxn = 0;
for (int i = 1; i <= cnt; i++) {
if (siz[i] > 1)maxn++;
}
cout << maxn << endl;
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (depth[i] == 1)cnt++;
}
cout << min(maxn, maxn - (cnt / 2) + 1) << " " << maxn << endl;
}
signed main() {
ios::sync_with_stdio();
cin.tie(0);
cout.tie(0);
int t;
cin >> t;
while (t--)
solve();
return 0;
}
最后
谢谢观看。

浙公网安备 33010602011771号