P8269 [USACO22OPEN] Visits S

洛谷上为什么没有题解 /yiw,那我就来用绿题难度的方法做这道黄题吧。

题目大意

Bessie 的 \(N\)\(2\le N\le 10^5\))个奶牛伙伴(编号为 \(1\cdots N\))每一个都拥有自己的农场。对于每个 \(1\le i\le N\),伙伴 \(i\) 想要访问伙伴 \(a_i\)\(a_i\neq i\))。

给定 \(1\ldots N\) 的一个排列 \((p_1,p_2,\ldots, p_N)\),访问按以下方式发生。

对于 \(1\)\(N\) 的每一个 \(i\)

  • 如果伙伴 \(a_{p_i}\) 已经离开了她的农场,则伙伴 \(p_i\) 仍然留在她的农场。
  • 否则,伙伴 \(p_i\) 离开她的农场去访问伙伴 \(a_{p_i}\) 的农场。这次访问会产生快乐的哞叫 \(v_{p_i}\) 次(\(0\le v_{p_i}\le 10^9\))。

对于所有可能的排列 \(p\),计算所有访问结束后可能得到的最大哞叫次数。

大体思路

首先,我们将所有 \((i,a_i)\) 连上有向边。这样,共有 \(n\) 个节点 \(n\) 条边,形成的是一个内向树的森林。

我们单独考虑每一棵内向树,这个内向树上必然有且仅有一个简单环,其余每个节点的出度为 \(1\)

我们对非成环的节点按照拓扑序,让当前一个入读为 \(0\) 的节点 \(u\) 取到 \(a_u\),并获得 \(v_u\) 的收益。可以证明,通过这样的方式能够使得所有非成环节点的 \(v_u\) 都被收集到。

对于成环的节点,这相当于一个大小 \(>1\) 的强连通分量。所以,我们可以使用 \(\text{Tarjan}\) 算法求出这个强连通分量 \(\mathbf{SCC}\)。假设环上节点 \(x\) 最先去访问 \(a_x\),则 \(a_y=x\) 的环上节点 \(y\) 无法访问到 \(x\),也就是说有且仅有 \(v_y\) 无法收集到。那么,我们根据贪心,只需要让 \(y\) 是所有环上节点中点权最小的即可。

注意原图可能是一个森林,因此需要多次 \(\text{Tarjan}\),分别减去每个环上点权的最小值。

综上,假设有 \(c\) 个环,答案为 \(\sum v_u-\sum_{i\in [1,c]}\min\{v_u\ |\ u\in \mathbf {SCC}_i\}\),时间复杂度 \(O(n)\)

完整代码

#include <bits/stdc++.h>
using namespace std;
#define rep(ii,aa,bb) for(re int ii = aa; ii <= bb; ii++)
#define Rep(ii,aa,bb) for(re int ii = aa; ii >= bb; ii--)
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair<int, int> PII;
const int maxn = 1e5 + 5;
const ll inf = 1e9 + 7;
namespace IO_ReadWrite {
	#define re register
	#define gg (p1 == p2 && (p2 = (p1 = _buf) + fread(_buf, 1, 1<<21, stdin), p1 == p2) ? EOF :*p1++)
	char _buf[1<<21], *p1 = _buf, *p2 = _buf;
	template <typename T>
	inline void read(T &x){
		x = 0; re T f=1; re char c = gg;
		while(c > 57 || c < 48){if(c == '-') f = -1;c = gg;}
		while(c >= 48 &&c <= 57){x = (x<<1) + (x<<3) + (c^48);c = gg;}
		x *= f;return;
	}
	inline void ReadChar(char &c){
		c = gg;
		while(!isalpha(c)) c = gg;
	}
	template <typename T>
	inline void write(T x){
		if(x < 0) putchar('-'), x = -x;
		if(x > 9) write(x/10);
		putchar('0' + x % 10);
	}
	template <typename T>
	inline void writeln(T x){write(x); putchar('\n');}
}
using namespace IO_ReadWrite;
ll n, val[maxn], mi[maxn], sz[maxn];
ll hd[maxn], ver[maxn], nxt[maxn], tot, timer, dfn[maxn], low[maxn];
inline void add(ll u, ll v) {
	ver[++tot] = v; nxt[tot] = hd[u]; hd[u] = tot;
}
ll scc[maxn], cnt, stk[maxn], top;
inline void tarjan(ll u) {
	dfn[u] = low[u] = ++timer;
	stk[++top] = u;
	for(int i = hd[u]; i; i = nxt[i]) {
		int v = ver[i];
		if(!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if(!scc[v]) 
			low[u] = min(low[u], dfn[v]);
	}
	if(low[u] == dfn[u]) {
		mi[++cnt] = inf;
		while(1) {
			int v = stk[top --];
			scc[v] = cnt;
			sz[cnt] ++; 
			mi[cnt] = min(mi[cnt], val[v]);
			if(u == v) break;
		}
	}
}
int main () {
	ll sum = 0;
	read(n);
	rep(i, 1, n) {
		ll u; 
		read(u); read(val[i]);
		add(i, u);
		sum += val[i];
	}
	rep(i, 1, n)
		if(!dfn[i]) tarjan(i);
	rep(i, 1, cnt) 
		if(sz[i] > 1) sum -= mi[i];
	writeln(sum);	
	return 0;
}
posted @ 2022-04-08 17:36  Mars_Dingdang  阅读(132)  评论(0)    收藏  举报