思路
这道题中,图的遍历起点必然是可控制的,如果起点不可控制,答案肯定是NO,NO的情况只需要从1~n找到第一个没被遍历过的数输出就可以了。
因此,我们记录下每个节点的入度,那么就相当于从图中所有入度为0(且可以控制)的点开始拓扑排序,并记录访问节点即可。
但是题目不保证是有向无环图,因此我们需要对环进行缩点处理。这个环有可能是途中的环,也有可能称为一个起点。
对于中途的环,缩点之后我们并不需要知道这个点的花费,因为他们都可以从起点开始被控制。
不确定起点的环,我们可以随意从一个点出发tarjan遍历,然后将该连通分量所需支付金额替换成环中最小的支付金额即可。(可在退栈中实现)
代码
#include <bits/stdc++.h> using namespace std; const int maxn = 3005; struct E //存边 { int next, to; } e[maxn << 2]; int n, m, p, r,id, a, b, ans; int ct[maxn],st[maxn], ru[maxn]; //ct为雇佣花费,st标记环的起点,ru为入度 int cnt, head[maxn]; //用于存图 int dfn[maxn], low[maxn], vis[maxn], bel[maxn], idx; //用于tarjan,dfn为访问时间,low为可回溯的最早时间 //vis为可访问标记,bel记录每个点所属连通分量,idx为时间戳 int stk[maxn], top; //用于栈,存储连通分量 void addedge(int from, int to) { e[++cnt].next = head[from]; e[cnt].to = to; head[from] = cnt; } void tarjan(int x) { dfn[x] = low[x] = ++idx; vis[x] = 1; stk[++top] = x; for (int i = head[x]; i; i = e[i].next) { int to = e[i].to; if (!dfn[to]) { tarjan(to); //遍历下一个节点 low[x] = min(low[x], low[to]); //最早访问时间 } else if (vis[to]) { low[x] = min(low[x], dfn[to]); } } if (dfn[x] == low[x]) { int y; while (y = stk[top--]) { bel[y] = x; vis[y] = 0; if (st[y]) //缩点操作 { if (ct[x]) { ct[x] = min(ct[x], ct[y]); //如果都能买,只需要买最小的 } else { ct[x] = ct[y]; //否则先买能买的 } } if (x == y) { break; } } } } signed main() { ios::sync_with_stdio(0); //读入优化 cin.tie(0); cout.tie(0); cin >> n; cin >> p; for (int i = 1; i <= p; i++) { cin >> id >> ct[id]; } cin >> r; for (int i = 1; i <= r; i++) { cin >> a >> b; addedge(a, b); ru[b]++; //记录入度 } for (int i = 1; i <= n; i++) { if (!ru[i] && ct[i]) //入度为0并且可支付必作为起点 { ans += ct[i]; //答案加上 tarjan(i); //开始遍历 } } for (int i = 1; i <= n; i++) { //可能有成环的情况 if (!dfn[i] && ct[i]) //没有遍历过并且可以被收买 { st[i] = 1; //环的起点 tarjan(i); //开始遍历 } } for (int i = 1; i <= n; i++) { //还是有节点没被访问到,那么不能控制所有间谍 if (!dfn[i]) { cout << "NO" << endl; cout << i << endl; return 0; } } for (int i = 1; i <= n; i++) { if (st[i]) {//可以控制所有的间谍,答案加上环的费用 ans += ct[i]; } } cout << "YES" << endl; cout << ans << endl; return 0; }