题解 【P6960 [NEERC2017]Interactive Sort】
标签:二分、分块思想,平衡树
题意
交互题。
有 \([1,n]\) \(n\) 个数字,按奇偶分为两部分,显然偶数有 \(\lfloor \frac{n}{2} \rfloor\) 个,奇数有 \(\lceil \frac{n}{2}\rceil\) 个,两部分打乱。
每次可以询问偶数部分第 \(i\) 个数和奇数部分 \(j\) 个数之间的大小关系,问在 \(300000\) 次内求出所有位置的数。
\(n \le 10000\)
思路
看到询问次数大概是 \(n\) 范围的 \(10\) 多倍,以及交互题的特性,联想到二分。
而我们又可以发现一个美好的性质,将某个偶数与所有奇数做一次询问操作不但能确定这个偶数的大小,还能将所有的奇数按这个大小划分成两部分。
暴力一点,我们直接做 \(\mathcal O((\frac{n}{2})^2)\) 次询问,偶数和奇数都可以确定下来。
这显然不能通过此题,这时你会发现二分还没用。
这个方法的最大缺陷是对于所有的偶数的询问都没有较好地联系起来(询问得到的信息只使确定奇数更方便,并没有为之后的偶数的询问提供便捷),考虑怎样优化这部分。
(以下所述“区间”均为部分奇数最大值与最小值构成的区间)
假设第一次偶数已经将奇数划分为 \(A,B\) 两部分,而第二个偶数的询问 \(\lceil \frac{n}{2}\rceil\) 次,显然只能将其中一个区间划分成两小部分,对于另一个区间无任何作用,因为对于另一个区间,返回的都是大于或小于。
当询问更多时,后面的偶数的询问大多数都是无意义的。
因此,我们只要找到偶数所属(被包含的那个奇数区间)的那个区间的奇数,再在其中为其划分。
但是,因为我们一开始并不知道那个偶数的大小,我们只能二分所有区间,因为要比较,所以我们定义一个区间的标记值为任意一个奇数的值,所有区间的这个值显然是单调的。
我们拿偶数与之二分比较,但是它所属的那个区间可能返回是大于也可以是小于,但必定是单调的区间的分界线,所以我们把分界线的两个区间都拿来做一次划分操作,也只会增加常数级的询问。
为了更好地维护二分(即你划分了一个区间之后还要维护其单调性,这显然是普通的数组无法维护的),因为蒟蒻不理解为什么其他题解里面vector一个个冒泡排序时间复杂度是合理的,我使用了清新的平衡树维护:)。
注意:为了避免讨论奇数个数等于偶数个数的情况,我们多加一个奇数在数组末尾然后每次询问它的时候都直接返回 <
。
算一下询问的复杂度:\(\sum_{i=1}^{i\le \frac{n}{2}} \frac{2n}{i} + \log i =\mathcal O(n\log n)\)。 (期望复杂度)
时间复杂度:\(\sum_{i=1}^{i\le \frac{n}{2}} \log^2 i=\mathcal O(n\log n)\)。
代码
int a, id, root, ans[N + 10], h[N + 10];
bool fake;
char opt;
vector<int> e[N + 10], L, R;
struct node
{
int val, ls, rs, rnd, siz;
} s[N + 10];
inline int New(int n)
{
s[++id].val = n;
s[id].ls = s[id].rs = 0;
s[id].rnd = rand();
s[id].siz = 1;
return id;
}
inline void up(int n) { s[n].siz = s[s[n].ls].siz + s[s[n].rs].siz + 1; }
inline void split(int n, int val, int &x, int &y)
{
if (!n)
{
x = y = 0;
return;
}
if (s[n].val <= val)
x = n, split(s[n].rs, val, s[n].rs, y);
else
y = n, split(s[n].ls, val, x, s[n].ls);
up(n);
}
inline int merge(int x, int y)
{
if (!x || !y)
return x + y;
if (s[x].rnd < s[y].rnd)
{
s[x].rs = merge(s[x].rs, y);
up(x);
return x;
}
else
{
s[y].ls = merge(x, s[y].ls);
up(y);
return y;
}
}
inline void ins(int n)
{
int x, y;
split(root, n - 1, x, y);
root = merge(merge(x, New(n)), y);
}
inline int val(int rt, int n)
{
int res, now = rt;
while (1)
{
if (s[s[now].ls].siz >= n)
now = s[now].ls;
else if (n == s[s[now].ls].siz + 1)
return s[now].val;
else
n -= s[s[now].ls].siz + 1, now = s[now].rs;
}
}
inline void query(int l, int r)
{
if (r == (a + 1) / 2 && fake)
{
opt = '<';
return;
}
cout << "? " << l << ' ' << r << endl;
fflush(stdout);
cin >> opt;
}
inline bool solve(int n, int i)
{
L.clear();
R.clear();
for (int j = 0; j < e[n].size(); j++)
{
query(i, e[n][j]);
if (opt == '>')
L.PB(e[n][j]);
else
R.PB(e[n][j]);
}
if (!L.size() || !R.size())
return false;
ans[i] = n + (L.size() - 1) * 2 + 1;
ins(ans[i] + 1);
e[n] = L;
e[ans[i] + 1] = R;
}
inline void solvepair(int l1, int l2, int i)
{
bool res = solve(l1, i);
if (res)
return;
solve(l2, i);
}
signed main()
{
// freopen("in1.in", "r", stdin);
ios::sync_with_stdio(0);
a = read();
if ((a & 1) == 0)
a++, fake = 1;
ins(1);
for (int i = 1; i <= (a + 1) / 2; i++)
e[1].PB(i);
for (int i = 1; i <= a / 2; i++)
{
int l = 1, r = i, l1 = -1, l2 = -1;
while (l <= r)
{
int mid = (l + r) >> 1, tmp = val(root, mid);
query(i, e[tmp][0]);
if (opt == '<')
r = mid - 1, l2 = tmp;
else
l = mid + 1, l1 = tmp;
}
if (l1 == -1)
solve(l2, i);
else if (l2 == -1)
solve(l1, i);
else
solvepair(l1, l2, i);
}
cout << "! ";
for (int i = 1; i <= a / 2; i++)
cout << ans[i] << ' ';
for (int i = 1; i <= a; i += 2)
h[e[i][0]] = i;
for (int i = 1; i <= (a + 1) / 2; i++)
cout << h[i] << ' ';
return 0;
}