Codeforces 1498E Two Houses 题解 —— 如何用结论吊打标算

Codeforces 1498E Two Houses 题解 —— 如何用结论吊打标算

Translate

有一个 n 个点的竞赛图,其中 i 的入度为 ki,你可以进行交互,每次询问点对 (A,B),回答是否存在 AB 的路径,如果回答为 "Yes" 则不能再询问,否则可以继续询问。

求出一个点对 (A,B) ,其中 A,B 可以互相到达,并且 |kAkB| 最大。

3n500

Solution

提供一个 O(n) 且不需要询问的做法

简单来说是:按照入度从小到大排序,如果到前 i 个的入度和恰好为 i×(i1)/2,则出现了一个新的强连通分量,假设上一次符合条件的是 lst,则 [lst+1,i] 构成了一个新的强连通分量。

这样遍历一遍就可以求出每个强连通分量里有哪些点,就能统计出答案了。

排序选择计数排序,时间复杂度 O(n)

Code

//Code by do_while_true
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#define pp std::pair<int,int>
#define mp std::make_pair
#define fir first
#define sec second
template <typename T>
T& read(T& r) {
	r = 0; bool w = 0; char ch = getchar();
	while(ch < '0' || ch > '9') w = ch == '-' ? 1 : 0, ch = getchar();
	while(ch >= '0' && ch <= '9') r = r * 10 + (ch ^ 48), ch = getchar();
	return r = w ? -r : r;
}
const int N = 510;
int n, mx = -1, lst, sum, ansu, ansv, ct;
std::vector<int>vec[N];
pp a[N];
signed main() {
	read(n);
	for(int i = 1, x; i <= n; ++i) {
		read(x);
		vec[x].push_back(i);
	}
	for(int i = 0; i <= n; ++i)
		for(auto j : vec[i])
			a[++ct] = mp(i, j);
	for(int i = 1; i <= n; ++i) {
		sum += a[i].fir;
		if(sum == i * (i-1) / 2) {
			if(lst != i-1) {
				int now = a[i].fir - a[lst+1].fir;
				if(now > mx)
					mx = now,
					ansu = a[lst+1].sec,
					ansv = a[i].sec;
			}
			lst = i;
		}
	}
	printf("! %d %d\n", ansu, ansv);
	return 0;
}

Proof

竞赛图有个经典结论,其强连通缩点后的DAG呈类似于链状, 前面的所有点向后面的所有点连边。网上能搜到很多证明,这里就不重复论述了。

可以发现,拓扑序在前的SCC的任意一节点的入度严格小于拓扑序在后的SCC的任意一节点入度。因为前面的SCC的点必定向后面的SCC的点连边。

所以所有节点按照入度和从小到大排序后,同一个SCC的节点一定是连续的。


引理一:若一个竞赛图按照入度从小到大排序,仅有 i=n 满足前 i 的入度和为 i×(i1)/2,则这个竞赛图缩点后只有一个SCC。

原因很简单,如果有多个SCC,那么第一个SCC若以 j 结尾,一定满足入度和为 j×(j1)/2,与仅有 i=n 时满足不符。


Solution 中的结论的必要性比较显然,在此不多赘述。

考虑 Solution 中提到的步骤,由引理一得,若第一个满足条件的为 i ,则前 i 个节点在同一个SCC,因为前 i 个节点之间互相只有 i×(i1)/2 条边,而入度也正好统计到了这么多边,所以可以看成引理一的情况。

现在已经求出第一个SCC了,如果我们找到的第二个满足条件的点在 j

由于满足必要性,所以此时考虑前 j 个节点时,要不然是两个 SCC,要不然后面的组成不了SCC。

设第一个SCC为集合 L,其大小为 l。后面的点组成的集合为 R,其大小为 r

只考虑 L,则他们的入度和为 k=0l1k=l×(l1)/2,由于图为竞赛图,所以 L 中的每个点一定都与 R 中每个点相连,且这些有向边一定是朝向 R 中的点,这一部分的入度和为 l×r=k=lj1l

总的入度和为 j×(j1)/2=k=0j1k,发现这个求和与前面两个求和的差值恰好为 k=lj1kl=k=0r1k

也就是说,R 内部的点的入度和为 k=0r1k=r×(r1)/2,由引理一可得 R 可组成一个SCC。

这是两个的情况,注意到对于 L 来说,仅使用到了 L 内部的入度和为 l×(l1)/2 ,以及 L 中的每个点一定都与 R 中每个点相连,将 L 扩展为前 k 个SCC的点组成的集合同样满足这两条性质。

结论的充分性得证。


本文即使略去一些繁琐的,不必要的推导,仍显得篇幅略长。如有质疑或建议欢迎提出。

posted @   do_while_true  阅读(185)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?

This blog has running: 1845 days 1 hours 33 minutes 51 seconds

点击右上角即可分享
微信分享提示