间谍网络
题目链接:ybt金牌导航3-5-5 / luogu P1262
题目大意
有一些人,其中有一些人你可以抓他们,然后有费用。
然后一个人被抓到之后,他可以帮你抓一些人。
问你能不能用最小的费用把所有人都抓到,如果可以输出最小费用,否则输出编号最小的不能被抓到的人。
思路
这道题其实容易想出把人抓人看成连边。
那如果一个环内有一个人被抓了,那这个环都完蛋,直接上缩点。
那缩点要记录什么呢?你环内只用选一个人抓,那肯定是选费用最小的,所以要记录点中最小权值。(如果没有人能就用
−
1
-1
−1 表示)
然后你想到如果不是所有人都被抓到,那你就要找被被抓到的点中包含原来点中编号最小的,所以还要记录这个点包含原来的点中编号最小的。
那也不难知道就是找缩点后图中入度为 0 0 0 的点抓,如果这个点没得抓就是 N O NO NO,否则总费用就加上这个点要被抓的最小费用。
那接着问题就是如果是
N
O
NO
NO,怎么求没被抓的编号最小点。
考虑先找到可以被抓的点,那把图中可以被抓的点放进一个队列中,然后跑 bfs 得到所有可以被抓的点。
然后我们从入度为
0
0
0 的,不可以被抓的点也放进一个队列中,然后跑 bfs 找到所有不会被抓的点。(如果碰到之前遇到过或是能被抓的点都不能往下走)然后再这些点中包含原来点中编号最小的就是答案。
代码
#include<cstdio>
#include<queue>
#include<cstring>
#include<iostream>
using namespace std;
struct node {
int to, nxt;
}e[8001], e_[8001];
int n, p, cost[3001], x, y, r;
int le[3001], KK, dfn[3001], low[3001], in[3001];
int sta[3001], tmp, tot, size[3001], val[3001], inn[3001];
int le_[3001], KK_, ru[3001], minn[3001], minnans, ans;
queue <int> q, qq;
bool noans;
void add(int x, int y) {
e[++KK] = (node){y, le[x]}; le[x] = KK;
}
void tarjan(int now) {//缩点
dfn[now] = low[now] = ++tmp;
sta[++sta[0]] = now;
for (int i = le[now]; i; i = e[i].nxt)
if (!dfn[e[i].to]) {
tarjan(e[i].to);
low[now] = min(low[now], low[e[i].to]);
}
else if (!in[e[i].to]) low[now] = min(low[now], low[e[i].to]);
//缩点的时候记录这个点能能不能有人可以被你抓,这些可以被你抓的人中费用最小的那个(你要抓哪个),和他们之间最小的编号
if (dfn[now] == low[now]) {
in[now] = ++tot;
size[tot]++;
val[tot] = cost[now];
minn[tot] = now;
while (sta[sta[0]] != now) {
in[sta[sta[0]]] = tot;
size[tot]++;
if (val[tot] == -1) val[tot] = cost[sta[sta[0]]];
else if (cost[sta[sta[0]]] != -1) val[tot] = min(val[tot], cost[sta[sta[0]]]);
minn[tot] = min(minn[tot], sta[sta[0]]);
sta[0]--;
}
sta[0]--;
}
}
void add_(int x, int y) {
e_[++KK_] = (node){y, le_[x]}; le_[x] = KK_;
ru[y]++;
}
int main() {
memset(cost, -1, sizeof(cost));
scanf("%d %d", &n, &p);
for (int i = 1; i <= p; i++) {
scanf("%d %d", &x, &y);
cost[x] = y;
}
scanf("%d", &r);
for (int i = 1; i <= r; i++) {
scanf("%d %d", &x, &y);
add(x, y);
}
for (int i = 1; i <= n; i++)
if (!dfn[i]) tarjan(i);
for (int i = 1; i <= n; i++)
for (int j = le[i]; j; j = e[j].nxt)
if (in[i] != in[e[j].to])
add_(in[i], in[e[j].to]);
minnans = n + 1;
for (int i = 1; i <= n; i++) {
if (!ru[i]) {
if (val[i] == -1) {//有入度为 0 的点中没有你可以抓的人,说明抓不完所有的人
noans = 1;
q.push(i);
inn[i] = 1;
minnans = min(minnans, minn[i]);
}
else {
ans += val[i];
}
}
if (val[i] != -1) {//这个是用来找第一个不能被你抓的人要用的(找到那些可以被你抓的人)
qq.push(i);
inn[i] = -1;
}
}
while (!qq.empty()) {//跑 bfs 找到所有可以被你抓的点
int now = qq.front();
qq.pop();
for (int i = le_[now]; i; i = e_[i].nxt)
if (!inn[e_[i].to]) {
inn[e_[i].to] = -1;
qq.push(e_[i].to);
}
}
while (!q.empty()) {//跑 bfs 找到所有不能被你抓的点
int now = q.front();
q.pop();
for (int i = le_[now]; i; i = e_[i].nxt)
if (!inn[e_[i].to]) {//这里就说明如果碰到的是可以被抓的人也不能往下走
inn[e_[i].to] = 1;
q.push(e_[i].to);
minnans = min(minnans, minn[e_[i].to]);
}
}
if (noans) {
printf("NO\n");
printf("%d", minnans);
}
else {
printf("YES\n");
printf("%d", ans);
}
return 0;
}