题解:P14375 [JOISC 2018] 图书馆 / Library
题目传送门
思路
考虑一个集合 \(S\),现在想再加入一本书 \(x\)。
因为 Query(S) 返回的是集合 \(S\) 里的书形成的连续段数,所以得到如下结论:
- 如果加入 \(x\) 后段数变多了,则 \(x\) 与 \(S\) 中的书不相邻。
- 如果加入 \(x\) 后段数不变,则 \(x\) 与 \(S\) 中的书相邻。
所以只需比较 Query(S) 和 Query(S ∪ {x}),就能知道 \(x\) 是否与 \(S\) 中的书相邻。
具体实现
枚举 \(i\),每次查 Query({1,i})。如果返回 \(1\),说明 \(1\) 号书和 \(i\) 号书在书架上相邻,得到初始集合 \(\{1,i\}\)。其他的书全部放入 \(c\) 中。
考虑向左右两端扩展。现在序列只有两项,还剩下 \(N-2\) 本书没有位置。不断从两端往外找邻居:
- 左端:找与
ans[0]相邻的书,往左边插入。 - 右端:找与
ans[ans.size()-1]相邻的书,往右边插入。
由于直接枚举找复杂度爆炸,考虑二分。
观察到一个单调性:取 \(c\) 的一个前缀 \(P=\{c[0],\dots,c[mid]\}\),查询 Query(P) 和 Query(P ∪ {x}),下文记为 \(a\) 和 \(b\):
- 若 \(a \geq b\),说明 \(x\) 和 \(P\) 中某一本书相邻,相邻书在前缀中。
- 否则则不在前缀中。
因为相邻书只有一本,所以“在不在前缀中”这个判断随着前缀变长而单调。于是可以二分。
代码
:::success[AC Code]
#include <vector>
using namespace std;
int Query(const std::vector<int>& M);
void Answer(const std::vector<int>& res);
int n,v;
vector<int> ans,c;
int qry(vector<int> a)
{
vector<int> q(n,0);
for(int i=0;i<a.size();i++)
{
q[a[i]-1]=1;
}
return Query(q);
}
bool check(vector<int> a,int k)
{
int x=qry(a),y;
a.push_back(k);
y=qry(a);
return x>=y;
}
int nxt(int x,vector<int> &c)
{
int l=0,r=c.size()-1;
while(l<r)
{
int mid=(l+r)>>1;
if(check(vector<int>(c.begin(),c.begin()+mid+1),x))
{
r=mid;
}
else
{
l=mid+1;
}
}
if(l<c.size()&&qry({x,c[l]})==1)
{
return l;
}
else
{
return -1;
}
}
void kuo(int d)
{
while(c.size())
{
int x,id;
if(d==1)
{
x=ans[0];
}
else
{
x=ans[ans.size()-1];
}
id=nxt(x,c);
if(id==-1)break;
if(d==1)
{
ans.insert(ans.begin(),c[id]);
}
else
{
ans.push_back(c[id]);
}
c.erase(c.begin()+id);
}
}
void Solve(int N)
{
n=N;
if(n==1)
{
Answer({1});
return;
}
for(int i=2;i<=n;i++)
{
if(qry({1,i})==1)
{
v=i;
break;
}
}
for(int i=2;i<=n;i++)
{
if(i!=v)
{
c.push_back(i);
}
}
ans=vector<int>({1,v});
kuo(1);
kuo(2);
Answer(ans);
}
:::

浙公网安备 33010602011771号