神树可还行
我们令(1)为树根,那么如果要询问(x)是否在(y)子树中,就令(S = {1} , T = {x} , u = y),询问一下就可以知道了。
那么考虑先构造出一个这样的序列(a_i):对于树上的每一个节点(u),它的父亲在这个序列上的位置在它的前面。
考虑增量构造。假如说我们已经构造出了(1)到(i)的序列,现在要把(i+1)插入。有一种显然正确的构造方法:在序列上二分,找到一个最大的位置(p)满足(S = {1} , T = {a_1,a_2,...,a_p} , u = i+1)时询问答案为(0),将这个数放在(a_p)之后即可。
接下来我们可以找边了。对于每一个点,我们找它的所有儿子,而它的儿子一定在序列的后面的位置。于是在(a_i)上从右往左扫,用一个vector维护当前未找到父亲的点的集合(P)。对于点(i),先询问(S = {1} , T = P , u = a_i)时是否存在答案,如果不存在直接退出,否则二分出在(P)中最靠前的儿子(P_j),连上边((i,P_j)),然后再对于(P' = {P_{j+1},P_{j+2},...,P_{|P|}})做这样的操作就可以了。
复杂度:构造序列需要(nlogn)、每一个儿子被找到需要(nlogn)、每一个点失败的询问总共(n)次,加起来(2nlogn+n)可以通过本题。
#include<iostream>
#include<cstdio>
#include<vector>
//This code is written by Itst
using namespace std;
#define PII pair < int , int >
int N;
vector < PII > Edge;
vector < int > S , T , arr , son;
bool query(int u){
cout << S.size() << endl;
for(auto t : S) cout << t << ' ';
cout << endl << T.size() << endl;
for(auto t : T) cout << t << ' ';
cout << endl << u << endl;
int x; cin >> x; return x;
}
void answer(){
cout << "ANSWER" << endl;
for(auto t : Edge) cout << t.first << ' ' << t.second << endl;
}
int main(){
ios::sync_with_stdio(0);
cin >> N;
S.push_back(1); arr.push_back(2);
for(int i = 3 ; i <= N ; ++i){
int L = 0 , R = i - 2;
while(L < R){
int mid = (L + R + 1) >> 1;
T.clear(); T.insert(T.begin() , arr.begin() , arr.begin() + mid);
query(i) ? R = mid - 1 : L = mid;
}
arr.insert(arr.begin() + L , i);
}
son.push_back(*--arr.end());
auto it = --arr.end();
while(it-- != arr.begin()){
auto t = son.begin();
while(t != son.end()){
T.clear(); T.insert(T.begin() , t , son.end());
if(!query(*it)) break;
int L = 0 , R = son.end() - t - 1;
while(L < R){
int mid = (L + R) >> 1;
T.clear(); T.insert(T.begin() , t , t + mid + 1);
query(*it) ? R = mid : L = mid + 1;
}
while(L){++t; --L;}
auto t1 = t; Edge.push_back(PII(*it , *t)); son.erase(t1);
}
son.push_back(*it);
}
for(auto t : son) Edge.push_back(PII(t , 1));
answer();
return 0;
}