“蔚来杯”2022牛客暑假多校训练营部分题解 1

本文介绍了两种算法解决有向图中最大化黑点数量的问题,一种是利用并查集维护黑点集合的动态更新,另一种是通过随机排列结合树形结构分析期望时间复杂度。

1J. Serval and Essay

题目大意

  • nnn 个点 mmm 条边的无重边无自环的有向图。
  • 初始时可以选择一个点染黑,其余点均为白点。
  • 若某点所有入边起点均为黑点,则该点可被染黑。
  • 最大化图中黑点数目。
  • 多组数据,1≤∑n≤2×105,1≤∑m≤5×1051 \le \sum n \le 2 \times 10^5, 1 \le \sum m\le 5\times 10^51n2×105,1m5×105

算法 1

  • SuS_uSu 表示 uuu 为初始黑点迭代得到的最终黑点集合,IuI_uIuuuu 所有入边起点构成的集合。
  • 显然 ∀u,v\forall u,vu,vSu,SvS_u, S_vSu,Sv 只能为包含或不交的关系。
  • 初始时 Su={ u}S_u = \{u\}Su={ u},迭代的过程即,若 ∃v,Iv⊆Su\exist v, I_v \subseteq S_uv,IvSu,则 Su←Su∪SvS_u \leftarrow S_u \cup S_vSuSuSv
  • 不断重复上述过程,直到找不到符合条件的 vvv,答案为 max⁡{ ∣Su∣}\max\{|S_u|\}max{ Su}
  • 用并查集维护 SuS_uSu
  • 初始时 Iv⊆SuI_v \subseteq S_uIvSu 就等价于 SvS_vSv 仅有一条来自 SuS_uSu 的入边。
  • 可以用 set\text{set}set 维护每个 SuS_uSu 所有出边指向的点的集合(除去指向 SuS_uSu 内部的点),合并 Su,SvS_u, S_vSu,Sv 时合并出边就能使上述等价条件在任意情况下成立,只要维护每个点的入度就能很容易找到点 vvv
  • 合并时采用启发式合并,时间复杂度 O((n+m)log⁡2n)\mathcal O((n + m)\log^2n)O((n+m)log2n)
#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
{
   
   
	char ch; bool flag = false; res = 0;
	while (ch = getchar(), !isdigit(ch) && ch != '-');
	ch == '-' ? flag = true : res = ch ^ 48;
	while (ch = getchar(), isdigit(ch))
		res = res * 10 + ch - 48;
	flag ? res = -res : 0;
}

template <class T>
inline void put(T x)
{
   
   
	if (x > 9)
		put(x / 10);
	putchar(x % 10 + 48);
}

template <class T>
inline void _put(T x)
{
   
   
	if (x < 0)
		x = -x, putchar('-');
	put(x);
}

template <class T>
inline void CkMin(T &x, T y) {
   
   x > y ? x = y : 0;}
template <class T>
inline void CkMax(T &x, T y) {
   
   x < y ? x = y : 0;}
template <class T>
inline T Min(T x, T y) {
   
   return x < y ? x : y;}
template <class T>
inline T Max(T x, T y) {
   
   return x > y ? x : y;}
template <class T>
inline T Abs(T x) {
   
   return x < 0 ? -x : x;}

using std::set;
using std::map;
using std::pair;
using std::string;
using std::vector;
using std::multiset;
using std::priority_queue;

typedef long long ll;
typedef long double ld;
typedef set<int>::iterator it;
const int Maxn = 1e9;
const int N = 2e5 + 5;
int n, top, T_data;
set<int> out[N];
int ind[N], pre[N], stk[N], sze[N], fa[N];

inline int ufs_find(int x)
{
   
   
	if (fa[x] != x)
		return fa[x] = ufs_find(fa[x]);
	return x;
}

inline void Merge(int x, int y)
{
   
   
	int tx = ufs_find(x), 
		ty = ufs_find(y);
	if (tx == ty)
		return ;
	if (out[tx].size() < out[ty].size())
		std::swap(tx, ty);
	fa[ty] = tx;
	sze[tx] += sze[ty];

	for (it e2 = out[ty].begin(); e2 != out[ty].end(); ++e2)
	{
   
   
		y = *e2;
		it e1 = out[tx].find(y);
		if (e1 == out[tx].end())
			out[tx].insert(y);
		else
		{
   
   
			--ind[y];
			if (ind[y] == 1)
				stk[++top] = y;
		}
	}
}

int main()
{
   
   
	read(T_data);
	for (int t = 1; t <= T_data; ++t)
	{
   
   
		read(n);
		top = 0;
		for (int i = 1; i <= n; ++i)
		{
   
   
			out[i].clear();
			fa[i] = i;
			sze[i] = 1;
		}
		for (int i = 1, x; i <= n; ++i)
		{
   
   
			read(ind[i]);
			for (int j = 1; j <= ind[i]; ++j)
			{
   
   
				read(x);
				out[x].insert(i);
			}
			pre[i] = x;
			if (ind[i] == 1)
				stk[++top] = i;
		}
		
		while (top)
		{
   
   
			int x = stk[top--];
			Merge(pre[x], x);
		}
		int ans = 0;
		for (int i = 1; i <= n; ++i)
			if (ufs_find(i) == i)
				CkMax(ans, sze[i]);
		printf("Case #%d: %d\n", t, ans);
	}
	return 0;
}

算法 2

  • 注意到若 v∈Suv \in S_uvSu,则 ∣Sv∣<∣Su∣|S_v| < |S_u|Sv<Su
  • 随机一个排列 PPP,若 pi∉Sp1∪Sp2∪⋯∪Spi−1p_i \notin S_{p_1} \cup S_{p_2} \cup \dots \cup S_{p_{i -1}}pi/Sp1Sp2Spi1,直接暴力求 SpiS_{p_i}Spi
  • SuS_uSu 的包含关系可以构成树形结构,需要暴力求 SpiS_{p_i}Spi 当且仅当它的所有祖先的 SuS_uSu 均还未求出。
  • 容易分析出这一做法的期望时间复杂度为 O((n+m)log⁡n)\mathcal O((n+m)\log n)O((n+m)logn)
#include <bits/stdc++.h>

template <class T>
inline void read(T &res)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值