【ybt金牌导航3-5-5】【luogu P1262】间谍网络

间谍网络

题目链接: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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值