做题随笔:P10451
Solution
题意
定义二元关系 \(\operatorname{S}\),其满足反对称性且不满足传递性。
现有互异元素 \(N(N \le 1000)\) 个,可以进行不多于 \(10000\) 次提问,每次提问可以得知两指定元素 \(a,b\) 的 \(a \operatorname{S}b\) 结果。求序列 \(a_N\) 满足:对任意 \(x_i,x_{i+1} \in a_N\),有 \(x_i \operatorname{S} x_{i+1}\)。
解释一下:
反对称性:对集合 \(X\) 上的二元关系 \(\operatorname{S}\),对任意 \(x_1,x_2 \in X(x_1 \ne x_2)\),若 \(x_1 \operatorname{S}x_2\),则 \(x_2 \operatorname{S} x_1\) 不成立。
例如:“小于”是实数集上的二元关系,它满足反对称性,即有:对任意 \(x_1,x_2 \in R(x_1 \ne x_2)\),若 \(x_1 < x_2\),则 \(x_2 <x_1\) 不成立。
传递性:对集合 \(X\) 上的二元关系 \(\operatorname{S}\),对任意 \(a,b,c \in X\)(\(a,b,c\) 互不相等),若 \(a \operatorname{S}b\) 且 \(b\operatorname{S}c\),则有 \(a \operatorname{S}c\) 成立。
例如:“小于”是实数集上的二元关系,它满足传递性,即有:对任意 \(a,b,c \in R\)(\(a,b,c\) 互不相等),若 \(a<b\) 且 \(b<c\),则有 \(a<c\) 成立。
由此可见,本题题面所述“小于”并非正常理解的小于,而是一种比较特殊的二元关系。基于图的思想和题目提示,我们可以尝试用图转化问题:
元素 \(\Rightarrow\) 节点 \(\hspace{1.5cm}\) 二元关系 \(\operatorname{S} \Rightarrow\) 边(\(a\operatorname{S}b\) 为真 \(\Rightarrow\) 有一条从 \(a\) 到 \(b\) 的有向边)
满足反对称性 \(\Rightarrow\) 没有无向边 \(\hspace{1.5cm}\) 不满足传递性 \(\Rightarrow\) 要求最终结果为链
(也即原题所述:也就是说,元素的大小关系是 \(N\) 个点与 \(\frac{N \times (N-1)}{2}\) 条有向边构成的任意有向图。)
所以,原题即为:在只能得知指定的 \(10000\) 条边的有向完全图(或称 竞赛图)中找一条 Hamilton 路径。
分析
1.有向完全图一定存在 Hamilton 路径的证明(可跳过)
为了论述的严谨性,我们先证明有向完全图一定存在 Hamilton 路径。(当然,可以直接从百度百科找定理秒,百科链接)
以下是一个自认比较简单,十分粗糙但相对好懂的证明(若有不完备还请指出):
考虑归纳法:
设有一个 \(n(n \ge 2)\) 阶有向完全图。
\(n =2\),显然成立;
设 \(n=k\) 时成立,对 \(n=k+1\),考虑如何添加一个点:
(如图,只画了 6 个点,但假设中间还有很多点,除了 Hamilton 路径和 $ \left \langle 1,6\right \rangle $ 间的边外的边省略)






现在考虑一下链首尾连接的情况(图 1 到图 4):对于图1这种成环的情况(情况 A),任意一点以任意取向都可以接入(如图 3(7-6-1-2-3-4-5)图 4(1-2-3-4-5-6-7)),直接不用看。重点是不成环的(图 5 到图 6)。
对图 5 情况,若新加点连边为 $ \left \langle 8,1\right \rangle $ 或 $ \left \langle 6,7\right \rangle$,则直接成立(情况 B);若为图 6 情况,考虑链上任意一点(此例为 3),若 3 与 7 之间连边为 $\left \langle 3,7\right \rangle $,则 3,4,5,6,7 部分与整体形似,可以类似上述考虑方式地分隔,直至达成情况 A 或情况 B,或者分隔出的联通块阶数为 2,一定成立。3 与 7 之间连边为 $ \left \langle 7,3 \right \rangle $ 同理。
于是 \(n=k+1\) 时成立,故结论得证。
2.算法思路
根据上述论述过程,我们也能够得到一种方法:在已有链上插入新加点,在不能直接接上链头和链尾时,可以枚举中间某点缩小一定存在可以插入处的链范围。于是我们可以想到一种形似二分的写法:每次插入新点时,提问 \(mid=(l+r) \div 2\) 与新加点的边的取向,从而确定某部分一定有可插入处,缩小范围直至确定,然后直接插入即可。
最大提问次数约 \(\sum_{i=1}^N {\log_2i} \approx 8529<10000\),可以通过此题。
不算插入的话时间复杂度为 \(O(n\log n)\),vector 暴力插入,于是成了 \(O(n^2)\)。但对于 \(N \le 1000\) 也是绰绰有余了。
实现
先选一个点进数组,然后进行拓展:每次拓展选取数组内现有数的中间一个,和新加点进行提问,根据方向缩小区间,直至可以直接连入时,暴力连入即可。
Code
#include <iostream>
#include <cstdio>
#include <cctype>
#include <vector>
using namespace std;
typedef long long ll;
ll fr() {
ll x=0,f=1;char c=getchar();
while(!isdigit(c)) {
if(c=='-') f=-1;
c=getchar();
}
while(isdigit(c)) {
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return x*f;
}//只读一个也要写快读的屑
inline bool compare(int a, int b)
{
cout << "? " << a << ' ' << b << endl;
bool t;
cin >> t;
return t;
}
const int maxn=1e3+100;
ll n;
vector<ll> ans(maxn);
int main(){
n=fr();
ans[1]=1;
for(register int i = 2; i <= n; i++){
int l=0,r=i,mid;//记得从0开始,不然左端接不上
while(l+1 < r) {//分割链长为2合法,不是一般的l<r的写法
mid=(l+r)>>1;
if(compare(i,ans[mid])) r=mid;
else l=mid;
}
ans.insert(ans.begin()+r,i);
}
printf("!");
for(register int i = 1; i <= n; i++) printf(" %lld",ans[i]);
return 0;
}
闲话
如果觉得有用,还请点个赞吧!

浙公网安备 33010602011771号