思路
这道题中,图的遍历起点必然是可控制的,如果起点不可控制,答案肯定是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;
} 
京公网安备 11010502036488号