【缩点 强连通分量】P1262 间谍网络|普及+

发布于:2025-07-29 ⋅ 阅读:(35) ⋅ 点赞:(0)

本文涉及知识点

C++图论 缩点 强连通分量

P1262 间谍网络

题目描述

由于外国间谍的大量渗入,国家安全正处于高度的危机之中。如果 A 间谍手中掌握着关于 B 间谍的犯罪证据,则称 A 可以揭发 B。有些间谍收受贿赂,只要给他们一定数量的美元,他们就愿意交出手中掌握的全部情报。所以,如果我们能够收买一些间谍的话,我们就可能控制间谍网中的每一分子。因为一旦我们逮捕了一个间谍,他手中掌握的情报都将归我们所有,这样就有可能逮捕新的间谍,掌握新的情报。

我们的反间谍机关提供了一份资料,包括所有已知的受贿的间谍,以及他们愿意收受的具体数额。同时我们还知道哪些间谍手中具体掌握了哪些间谍的资料。假设总共有 n n n 个间谍( n n n 不超过 3000 3000 3000),每个间谍分别用 1 1 1 3000 3000 3000 的整数来标识。

请根据这份资料,判断我们是否有可能控制全部的间谍,如果可以,求出我们所需要支付的最少资金。否则,输出不能被控制的一个间谍。

输入格式

第一行只有一个整数 n n n

第二行是整数 p p p。表示愿意被收买的人数, 1 ≤ p ≤ n 1\le p\le n 1pn

接下来的 p p p 行,每行有两个整数,第一个数是一个愿意被收买的间谍的编号,第二个数表示他将会被收买的数额。这个数额不超过 20000 20000 20000

紧跟着一行只有一个整数 r r r 1 ≤ r ≤ 8000 1\le r\le8000 1r8000。然后 r r r 行,每行两个正整数,表示数对 ( A , B ) (A, B) (A,B) A A A 间谍掌握 B B B 间谍的证据。

输出格式

如果可以控制所有间谍,第一行输出 YES,并在第二行输出所需要支付的贿金最小值。否则输出 NO,并在第二行输出不能控制的间谍中,编号最小的间谍编号。

输入输出样例 #1

输入 #1

3
2
1 10
2 100
2
1 3
2 3

输出 #1

YES
110

输入输出样例 #2

输入 #2

4
2
1 100
4 200
2
1 2
3 4

输出 #2

NO
3

缩点 无向图寻找环

简化: 存在某个节点能访问所有节点,如节点0。

m_vTime[cur]记录cur的DFS序。未处理的节点,m_vTime[cur]等于-1。已经处理的节点,DFS直接返回,保证每个点只DFS一次。
性质一:cur直接或间接DFS到的任何节点node,m_vTime[cur] < m_vTime[node]。则node一定是cur的后代。且DFS(cur)结束时,cur所有的后代都访问结束。
vPar[cur]记录cur的父节点,由于只会DFS一次,除根节点外,故只有一个父节点。
一,树边。 父节点 → 子节点 父节点\rightarrow 子节点 父节点子节点。DSF序小指向DFS序大。
二,前向边。 祖先 → 后代 祖先\rightarrow 后代 祖先后代,全部删除不影响环。任意祖先通过树边可以到达任意后代。DSF序小指向DFS序大。
三,后向边(回边)。 后代 → 祖先 后代\rightarrow 祖先 后代祖先DSF序大指向DFS序小。
四,横向边。其它边。 D S F 序大指向 D F S 序小。 ∗ ∗ 性质二 ∗ ∗ :任意环一定不包括横边,令横边 DSF序大指向DFS序小。 **性质二**:任意环一定不包括横边,令横边 DSF序大指向DFS序小。性质二:任意环一定不包括横边,令横边u\rightarrow v$,由于是环v也能访问u,且DFS序小,根据性质一是回边。
性质三:只有树边无法构成环,DFS只会越来越大;同理,只有回边,也无环。环必须有树边和回边。
性质四:一个环有多条回边,可以拆分若干个只有一条回边的环(我简称基础环)。
时间复杂度:O(边数+点数)

环的合并 多个环有共同点则是一个强连通区域

性质五:基础环的回边是 u → v u\rightarrow v uv,长度是len,则构成的点是: u 的 [ 0 到 l e n − 1 ] 级祖先 u的[0到len-1]级祖先 u[0len1]级祖先
极端情况下,长度n的链,回边 n × ( n − 1 ) 2 \frac{n\times(n-1)}{2} 2n×(n1),环长n。时间复杂度:O(nnn)
cnt[cur]=i,表示节点cur的0到i-1级祖先构成环。如果cur有多个环,取cnt[cur]最大的。
m_vSortToNode[i] 记录DFS序为i的节点。
通过cur逆序访问m_vSortToNode。如果cur存在父节点,且cnt[cur] > 0{uf.连通cur和父节点。MaxSelf(cnt[父节点],cur[cur]-1)}
uf中的连通区域可以缩成一个点。
时间复杂度:O(n)

不知道是否有点连通所有点

DFS(0…N-1)。由于环上任意一点,可以访问环上其它点,故DFS到环上任意一点,则可以DFS到整个环。即环不会分成两部分。
注意:DFS的时候,生成各点层次、父节点、各节点DFS序、各DFS序对应节点。
DFS(当前节点,父节点,层次)

缩点

如果存在环,缩成点,收买价格取最小值。
出掉自环,重边出不出都可以。
所有入度为0的间谍都要收买。

如果存在无法收买的间谍

缩点后的有向图G1的临接表为neiBo1,can记录所有能够收买的间谍组(一个间谍对应原图的一个点,一组间谍对应G1的一个点)。
以can为0层,求层次。层次-1的间谍组无法收买。求无法收买的间谍组的间谍的最小编号。

代码

核心代码

#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>
#include<array>

#include <bitset>
using namespace std;

template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
	in >> pr.first >> pr.second;
	return in;
}

template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t);
	return in;
}

template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
	return in;
}

template<class T1, class T2, class T3, class T4, class T5, class T6, class T7 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4,T5,T6,T7>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t) >> get<4>(t) >> get<5>(t) >> get<6>(t);
	return in;
}

template<class T = int>
vector<T> Read() {
	int n;
	cin >> n;
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}
template<class T = int>
vector<T> ReadNotNum() {
	vector<T> ret;
	T tmp;
	while (cin >> tmp) {
		ret.emplace_back(tmp);
		if ('\n' == cin.get()) { break; }
	}
	return ret;
}

template<class T = int>
vector<T> Read(int n) {
	vector<T> ret(n);
	for (int i = 0; i < n; i++) {
		cin >> ret[i];
	}
	return ret;
}

template<int N = 1'000'000>
class COutBuff
{
public:
	COutBuff() {
		m_p = puffer;
	}
	template<class T>
	void write(T x) {
		int num[28], sp = 0;
		if (x < 0)
			*m_p++ = '-', x = -x;

		if (!x)
			*m_p++ = 48;

		while (x)
			num[++sp] = x % 10, x /= 10;

		while (sp)
			*m_p++ = num[sp--] + 48;
		AuotToFile();
	}
	void writestr(const char* sz) {
		strcpy(m_p, sz);
		m_p += strlen(sz);
		AuotToFile();
	}
	inline void write(char ch)
	{
		*m_p++ = ch;
		AuotToFile();
	}
	inline void ToFile() {
		fwrite(puffer, 1, m_p - puffer, stdout);
		m_p = puffer;
	}
	~COutBuff() {
		ToFile();
	}
private:
	inline void AuotToFile() {
		if (m_p - puffer > N - 100) {
			ToFile();
		}
	}
	char  puffer[N], * m_p;
};

template<int N = 1'000'000>
class CInBuff
{
public:
	inline CInBuff() {}
	inline CInBuff<N>& operator>>(char& ch) {
		FileToBuf();
		while (('\r' == *S) || ('\n' == *S) || (' ' == *S)) { S++; }//忽略空格和回车
		ch = *S++;
		return *this;
	}
	inline CInBuff<N>& operator>>(int& val) {
		FileToBuf();
		int x(0), f(0);
		while (!isdigit(*S))
			f |= (*S++ == '-');
		while (isdigit(*S))
			x = (x << 1) + (x << 3) + (*S++ ^ 48);
		val = f ? -x : x; S++;//忽略空格换行		
		return *this;
	}
	inline CInBuff& operator>>(long long& val) {
		FileToBuf();
		long long x(0); int f(0);
		while (!isdigit(*S))
			f |= (*S++ == '-');
		while (isdigit(*S))
			x = (x << 1) + (x << 3) + (*S++ ^ 48);
		val = f ? -x : x; S++;//忽略空格换行
		return *this;
	}
	template<class T1, class T2>
	inline CInBuff& operator>>(pair<T1, T2>& val) {
		*this >> val.first >> val.second;
		return *this;
	}
	template<class T1, class T2, class T3>
	inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {
		*this >> get<0>(val) >> get<1>(val) >> get<2>(val);
		return *this;
	}
	template<class T1, class T2, class T3, class T4>
	inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {
		*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);
		return *this;
	}
	template<class T = int>
	inline CInBuff& operator>>(vector<T>& val) {
		int n;
		*this >> n;
		val.resize(n);
		for (int i = 0; i < n; i++) {
			*this >> val[i];
		}
		return *this;
	}
	template<class T = int>
	vector<T> Read(int n) {
		vector<T> ret(n);
		for (int i = 0; i < n; i++) {
			*this >> ret[i];
		}
		return ret;
	}
	template<class T = int>
	vector<T> Read() {
		vector<T> ret;
		*this >> ret;
		return ret;
	}
private:
	inline void FileToBuf() {
		const int canRead = m_iWritePos - (S - buffer);
		if (canRead >= 100) { return; }
		if (m_bFinish) { return; }
		for (int i = 0; i < canRead; i++)
		{
			buffer[i] = S[i];//memcpy出错			
		}
		m_iWritePos = canRead;
		buffer[m_iWritePos] = 0;
		S = buffer;
		int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);
		if (readCnt <= 0) { m_bFinish = true; return; }
		m_iWritePos += readCnt;
		buffer[m_iWritePos] = 0;
		S = buffer;
	}
	int m_iWritePos = 0; bool m_bFinish = false;
	char buffer[N + 10], * S = buffer;
};

class CNeiBo
{
public:
	static vector<vector<int>> Two(int n, const vector<pair<int, int>>& edges, bool bDirect, int iBase = 0)
	{
		vector<vector<int>>  vNeiBo(n);
		for (const auto& [i1, i2] : edges)
		{
			vNeiBo[i1 - iBase].emplace_back(i2 - iBase);
			if (!bDirect)
			{
				vNeiBo[i2 - iBase].emplace_back(i1 - iBase);
			}
		}
		return vNeiBo;
	}
	static vector<vector<int>> Two(int n, const vector<vector<int>>& edges, bool bDirect, int iBase = 0)
	{
		vector<vector<int>>  vNeiBo(n);
		for (const auto& v : edges)
		{
			vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase);
			if (!bDirect)
			{
				vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase);
			}
		}
		return vNeiBo;
	}
	static vector<vector<std::pair<int, int>>> Three(int n, vector<vector<int>>& edges, bool bDirect, int iBase = 0)
	{
		vector<vector<std::pair<int, int>>> vNeiBo(n);
		for (const auto& v : edges)
		{
			vNeiBo[v[0] - iBase].emplace_back(v[1] - iBase, v[2]);
			if (!bDirect)
			{
				vNeiBo[v[1] - iBase].emplace_back(v[0] - iBase, v[2]);
			}
		}
		return vNeiBo;
	}
	static vector<vector<std::pair<int, int>>> Three(int n, const vector<tuple<int, int, int>>& edges, bool bDirect, int iBase = 0)
	{
		vector<vector<std::pair<int, int>>> vNeiBo(n);
		for (const auto& [u, v, w] : edges)
		{
			vNeiBo[u - iBase].emplace_back(v - iBase, w);
			if (!bDirect)
			{
				vNeiBo[v - iBase].emplace_back(u - iBase, w);
			}
		}
		return vNeiBo;
	}
	static vector<vector<int>> Mat(vector<vector<int>>& neiBoMat)
	{
		vector<vector<int>> neiBo(neiBoMat.size());
		for (int i = 0; i < neiBoMat.size(); i++)
		{
			for (int j = i + 1; j < neiBoMat.size(); j++)
			{
				if (neiBoMat[i][j])
				{
					neiBo[i].emplace_back(j);
					neiBo[j].emplace_back(i);
				}
			}
		}
		return neiBo;
	}
};

class CBFSLeve {
public:
	static vector<int> Leve(const vector<vector<int>>& neiBo, vector<int> start) {
		vector<int> leves(neiBo.size(), -1);
		for (const auto& s : start) {
			leves[s] = 0;
		}
		for (int i = 0; i < start.size(); i++) {
			for (const auto& next : neiBo[start[i]]) {
				if (-1 != leves[next]) { continue; }
				leves[next] = leves[start[i]] + 1;
				start.emplace_back(next);
			}
		}
		return leves;
	}
	template<class NextFun>
	static vector<int> Leve(int N, NextFun nextFun, vector<int> start) {
		vector<int> leves(N, -1);
		for (const auto& s : start) {
			leves[s] = 0;
		}
		for (int i = 0; i < start.size(); i++) {
			auto nexts = nextFun(start[i]);
			for (const auto& next : nexts) {
				if (-1 != leves[next]) { continue; }
				leves[next] = leves[start[i]] + 1;
				start.emplace_back(next);
			}
		}
		return leves;
	}
	static vector<vector<int>> LeveNodes(const vector<int>& leves) {
		const int iMaxLeve = *max_element(leves.begin(), leves.end());
		vector<vector<int>> ret(iMaxLeve + 1);
		for (int i = 0; i < leves.size(); i++) {
			ret[leves[i]].emplace_back(i);
		}
		return ret;
	};
	static vector<int> LeveSort(const vector<int>& leves) {
		const int iMaxLeve = *max_element(leves.begin(), leves.end());
		vector<vector<int>> leveNodes(iMaxLeve + 1);
		for (int i = 0; i < leves.size(); i++) {
			leveNodes[leves[i]].emplace_back(i);
		}
		vector<int> ret;
		for (const auto& v : leveNodes) {
			ret.insert(ret.end(), v.begin(), v.end());
		}
		return ret;
	};
};

class CSCCTarjan {
public:
	CSCCTarjan(vector<vector<int>>& neiBo) :m_neiBo(neiBo) {
		const int N = neiBo.size();
		m_vTime.assign(N, -1);
		m_vBack.assign(N, -1);
		m_vIsStack.assign(N, false);
		for (int i = 0; i < N; i++) {
			DFS(i);
		}
	}
	vector<vector<int>> m_sccs;
protected:
	void DFS(int cur) {
		if (-1 != m_vTime[cur]) { return; }
		m_vTime[cur] = m_vBack[cur] = m_iTimes++;
		m_vIsStack[cur] = true;
		m_sta.emplace(cur);
		for (const auto& next : m_neiBo[cur]) {
			if (-1 == m_vTime[next]) {
				DFS(next);
				m_vBack[cur] = min(m_vBack[cur], m_vBack[next]);
			}
			else if (m_vIsStack[next]) {
				m_vBack[cur] = min(m_vBack[cur], m_vTime[next]);
			}
		}
		if (m_vTime[cur] != m_vBack[cur]) { return; }
		vector<int> scc;
		while (m_sta.size())
		{
			auto u = m_sta.top(); m_sta.pop();
			scc.emplace_back(u);
			m_vIsStack[u] = false;
			if (cur == u) { break; }
		}
		m_sccs.emplace_back(scc);
	}
	vector<vector<int>>& m_neiBo;
	int  m_iTimes = 0;
	vector<int> m_vTime, m_vBack;
	vector<bool> m_vIsStack;
	stack<int> m_sta;
};
class Solution {
public:
	pair<bool, int> Ans(const int N, vector<pair<int, int>>& vNoMoney, vector<pair<int, int>>& edge) {
		vector<int> need(N, INT_MAX / 2);
		for (const auto& [v, Mon] : vNoMoney) {
			need[v - 1] = Mon;
		}
		auto neiBo = CNeiBo::Two(N, edge, true, 1);
		CSCCTarjan sp(neiBo);
		vector<vector<int>>& sccs = sp.m_sccs;
		vector<int> ptNew(N);
		iota(ptNew.begin(), ptNew.end(), 0);
		vector<int> v0;
		for (auto& v : sccs) {
			nth_element(v.begin(), v.begin(), v.end());
			v0.emplace_back(v[0]);
			for (int i = 1; i < v.size(); i++) {
				need[v[0]] = min(need[v[0]], need[v[i]]);
				ptNew[v[i]] = v[0];
			}
		}
		sort(v0.begin(), v0.end());
		vector<int> in(N);
		vector<vector<int>> neiBo1(N);
		for (int i = 0; i < N; i++) {
			const int p1 = ptNew[i];
			for (const auto& next : neiBo[i]) {
				const int p2 = ptNew[next];
				if (p1 == p2) { continue; }//忽略自环
				in[p2]++;
				neiBo1[p1].emplace_back(p2);
			}
		}

		int ans = 0;
		bool bCanAll = true;
		for (auto& n : v0) {
			if (0 != in[n]) { continue; }
			if (need[n] >= INT_MAX / 2) { bCanAll = false; break; }
			ans += need[n];
		}
		if (bCanAll) {
			return { true,ans };
		}
		vector<int> can;
		for (auto& n : v0) {
			if (need[n] >= INT_MAX / 2) { continue; }
			can.emplace_back(n);
		}
		auto leves = CBFSLeve::Leve(neiBo1, can);
		int iMin = INT_MAX / 2;
		for (const auto& n : v0) {
			if (-1 == leves[n]) { return { false,n + 1 }; }
		}
		return { true,-1 };
	}
};
int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	ios::sync_with_stdio(0); cin.tie(nullptr);
	//CInBuff<> in; COutBuff<10'000'000> ob;
	int n;
		cin >> n;
		auto noMoney = Read<pair<int, int>>();
		auto edge = Read<pair<int, int>>();
#ifdef _DEBUG	
		printf("N=%d",n);
		Out(noMoney, ",noMoney=");
		Out(edge, ",edge=");
#endif // DEBUG	
		auto res = Solution().Ans(n,noMoney,edge);
		cout << (res.first ? "YES" : "NO")<<"\n";
		cout << res.second << "\n";
	return 0;
};

单元测试

	 int N;
		vector<pair<int, int>> noMoney, edge;
		TEST_METHOD(TestMethod01)
		{
			N = 3, noMoney = { {1,10},{2,100} }, edge = { {1,3},{2,3} };
			auto res = Solution().Ans(N, noMoney,edge);
			AssertEx({true,110 }, res);
		}
		TEST_METHOD(TestMethod02)
		{
			N = 4, noMoney = { {1,100},{4,200} }, edge = { {1,2},{3,4} };
			auto res = Solution().Ans(N, noMoney, edge);
			AssertEx({ false,3 }, res);
		}
		TEST_METHOD(TestMethod03)
		{
			N = 4, noMoney = { {1,2},{2,2} ,{3,3},{4,4} }, edge = { {1,2},{1,3},{1,4},{4,3},{3,2},{2,1} };
			auto res = Solution().Ans(N, noMoney, edge);
			AssertEx({ true,2 }, res);
		}
		TEST_METHOD(TestMethod05)
		{
			N = 9, noMoney = { {1,100},{9,10000} }, edge = { {1,3},{4,3},{2,4},{2,1},{8,5},{8,7},{5,6},{7,6},{4,5} };
			auto res = Solution().Ans(N, noMoney, edge);
			AssertEx({ false,2 }, res);
		}
		TEST_METHOD(TestMethod06)
		{
			N = 4, noMoney = { {4,10} }, edge = { {3,2},{3,1} };
			auto res = Solution().Ans(N, noMoney, edge);
			AssertEx({ false,1 }, res);
		}
		TEST_METHOD(TestMethod04)
		{
			int n = 5; // Number of nodes
			vector<vector<int>> adj(n);

			// Example graph
			adj[0] = { 1 };
			adj[1] = { 2 };
			adj[2] = { 0, 3 };
			adj[3] = { 4 };
			adj[4] = { 3 };

			vector<vector<int>> sccs = CShrinkPoint().kosaraju(adj, n);
			
		}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。


网站公告

今日签到

点亮在社区的每一天
去签到