CF1027D
文章目录
题意
n间宿舍里有老鼠,老鼠(只有一只)可能出现在任何一间寝室,然后它在第i个寝室里待一秒后一定会跑向第c[i]个寝室,给出在每个寝室布置陷阱的成本,求无论老鼠初始出现在那个寝室,一定会被抓住的最低成本.
输入
第一行给出n(1<=n<=2e5)
第二行给出n个数字,每个大小都是[1,1e4],第i个表示c[i],即在第i个寝室布置陷阱的成本
第三行给出n个数组,每个大小都是[1,n],第i个表示a[i],即在第i个寝室出现后,下一个会跑到的寝室号
输出
最低成本
解析
此题不难发现,n个节点却有n个边,说明一定有环,(也有可能是自环),也就是在一个连通分量里(此题可能不连通),老鼠最终都会跑到环上,且在环上循环,故对于一个联通分量的最小值其实就是此联通分量上的环上的最小成本,再把所有联通分量上的最低成本加起来就是最终答案
代码
// AC 非常有趣的一个题,dfs+并查集.判断环路的
#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>
#define Endl endl
#define ll long long
using namespace std;
void ioInit() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
int n;
int c[200100];
int a[200100];
bool vis[200100];
bool visLoop[200100];
int F[200100];
bool qvis[200100];
void init() {
for (int i = 0; i <= n; ++i) {
F[i] = i;
}
}
int Find(int x) {
if (x != F[x]) {
F[x] = Find(F[x]);
}
return F[x];
}
void Union(int x, int y) {
int fx = Find(x);
int fy = Find(y);
if (fx != fy) {
F[fx] = fy;
}
}
// 直链则输出链最后一个元素的值
// 有环则输出环上最小的值
int circleDfs(int minn, int cur) {
// cout << "minnn : " << minn << " cur== " << cur << endl;
if (visLoop[a[cur]]) {
minn = min(minn, c[cur]);
return minn;
}
visLoop[a[cur]] = 1;
int ans = circleDfs(min(minn, c[cur]), a[cur]);
visLoop[a[cur]] = 0;
return ans;
}
int dfs(int cur) {
// cout << "dfs cur: " << cur << endl;
if (a[cur] == cur) {
return c[cur];
}
if (vis[a[cur]]) {
visLoop[cur] = 1;
int ans = circleDfs(c[cur], cur);
visLoop[cur] = 0;
return ans;
}
vis[a[cur]] = 1;
int ans = dfs(a[cur]);
vis[a[cur]] = 0;
return ans;
}
ll Cal() {
ll ans = 0;
for (int i = 1; i <= n; ++i) {
vis[i] = 1;
if (!qvis[Find(i)]) { // 用并查集来规避在同一个联通分支上的反复计算
ans += dfs(i);
qvis[Find(i)] = 1;
}
// cout << dfs(i) << endl;
vis[i] = 0;
}
return ans;
}
int main() {
ioInit();
cin >> n;
init();
for (int i = 1; i <= n; ++i) {
cin >> c[i];
}
for (int i = 1; i <= n; ++i) {
cin >> a[i];
Union(i, a[i]);
}
cout << Cal() << endl;
return 0;
}
启示
这题思路不难,但是在实现上我有些太过复杂,应该有更简单的做法