【CF 675D】 Tree Construction(离线二分+左右指针)
首先要找到一个结论:对于已添加第1~(i-1)个结点的树,添加第i个结点v时,父亲一定是之前添加的值最接近v的点。即min(|u-v|)的那个u
这样会出现u < v或者u > v(及左边最靠近v和右边最靠近v的)两种 一定是选择最晚出现的u来作为v的父亲。
因为对于两个u此时已经加入到树中,而且在没有v之前,uL与uR是相邻(连)的。及其中一个为另一个父亲
其实也就是较早出现的为较晚出现的那个点的父亲。
在线处理的话,如果能很快插入的话,保证序列有序,每次插入一个点前,二分出该点的左右邻点,找出其中最晚出现的,即为该点父亲。
我用的vector超时了,看他们有用set过的。。。没试
后来想到秃头,想出一个离线处理的方法。
先把所有点存下来,并按点值从小到大排序,然后去个重。同时,每个点记录最早出现的时刻,还有左右邻点。
然后从第n次加入到第2次加入的点遍历(读取的时候预先备份一下即可
二分找到这个点,然后比较下左右邻点,选取所需要的即可。
如果找某个点时,发现再往前没有该点,把他左右邻点链接即可。即为将该点扣去。
代码如下:
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int INF = 0x3f3f3f3f;
const int mod = 1e9+7;
const double eps = 1e-8;
struct Node
{
int x,mn,mx,l,r;
bool operator < (const struct Node a)const
{
return x < a.x;
}
};
Node nd[100100];
int x[100100];
int ans[100100];
int tp = 0;
int main()
{
int n,l,r,pos;
scanf("%d",&n);
for(int i = 0; i < n; ++i)
{
scanf("%d",&x[i]);
<span style="white-space:pre"> </span>nd[i].x = x[i];
nd[i].mn = nd[i].mx = i;
}
sort(nd,nd+n);
for(int i = 0; i < n; ++i)
{
if(tp == 0 || nd[i].x != nd[tp-1].x)
{
nd[tp++] = nd[i];
nd[tp-1].l = tp-2;
nd[tp-1].r = tp;
}
else
{
nd[tp-1].mn = min(nd[tp-1].mn,nd[i].mn);
nd[tp-1].mx = min(nd[tp-1].mn,nd[i].mx);
}
}
for(int i = n-1; i > 0; --i)
{
l = 0, r = tp-1;
while(l <= r)
{
int mid = (l+r)>>1;
if(nd[mid].x == x[i])
{
pos = mid;
break;
}
else if(nd[mid].x > x[i]) r = mid-1;
else l = mid+1;
}
if(nd[pos].l == -1) ans[i] = nd[nd[pos].r].x;
else if(nd[pos].r == tp) ans[i] = nd[nd[pos].l].x;
else if(nd[nd[pos].r].mn > nd[nd[pos].l].mn) ans[i] = nd[nd[pos].r].x;
else ans[i] = nd[nd[pos].l].x;
if(nd[pos].mn == i)
{
nd[nd[pos].l].r = nd[pos].r;
nd[nd[pos].r].l = nd[pos].l;
}
}
for(int i = 1; i < n; ++i)
{
if(i != 1) putchar(' ');
printf("%d",ans[i]);
}
return 0;
}