do_while_true

一言(ヒトコト)

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

\(\mathcal{Translate}\)

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

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

\(3\leq n\leq 500\)

\(\mathcal{Solution}\)

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

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

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

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

\(\mathcal{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;
}

\(\mathcal{Proof}\)

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

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

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


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

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


\(\mathcal{Solution}\) 中的结论的必要性比较显然,在此不多赘述。

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

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

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

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

只考虑 \(L\),则他们的入度和为 \(\sum\limits_{k=0}^{l-1}k=l\times(l-1)/2\),由于图为竞赛图,所以 \(L\) 中的每个点一定都与 \(R\) 中每个点相连,且这些有向边一定是朝向 \(R\) 中的点,这一部分的入度和为 \(l\times r=\sum\limits_{k=l}^{j-1}l\)

总的入度和为 \(j\times(j-1)/2=\sum\limits_{k=0}^{j-1}k\),发现这个求和与前面两个求和的差值恰好为 \(\sum\limits_{k=l}^{j-1}k-l=\sum\limits_{k=0}^{r-1}k\)

也就是说,\(R\) 内部的点的入度和为 \(\sum\limits_{k=0}^{r-1}k=r\times(r-1)/2\),由引理一可得 \(R\) 可组成一个SCC。

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

结论的充分性得证。


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

posted @ 2021-04-06 16:16  do_while_true  阅读(184)  评论(0编辑  收藏  举报