zoukankan      html  css  js  c++  java
  • Codeforces 1498E Two Houses 题解 —— 如何用结论吊打标算

    (mathcal{Translate})

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

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

    (3leq nleq 500)

    (mathcal{Solution})

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

    简单来说是:按照入度从小到大排序,如果到前 (i) 个的入度和恰好为 (i imes(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
    ", ansu, ansv);
    	return 0;
    }
    

    (mathcal{Proof})

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

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

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


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

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


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

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

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

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

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

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

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

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

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

    结论的充分性得证。


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

  • 相关阅读:
    《转》2013年那些深入人心的小故事
    sklearn学习2-----LogisticsRegression
    sklearn学习汇总
    sklearn学习1----sklearn.SVM.SVC
    树(5)-----判断两颗树一样或者一棵树是否是另外一颗的子树
    树(4)-----树的高度
    面试题1-----SVM和LR的异同
    算法19-----(位运算)找出数组中出现只出现一次的数
    树(3)-----栈(迭代)
    python中的全局变量、局部变量、实例变量
  • 原文地址:https://www.cnblogs.com/do-while-true/p/14622363.html
Copyright © 2011-2022 走看看