题解: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);
}

:::

posted @ 2026-09-10 15:00  kevin1426730  阅读(8)  评论(0)    收藏  举报