2017年9月1号提高组 T3 玫瑰花精
Description
Input
第一行两个正整数n,m,分别表示花园大小和操作数。
接下来m行,每行两个正整数,F和x。
F=1:表示编号为x的花精进入花园。
F=2:表示编号为x的花精离开花园。
保证操作合法,即:离开花园的花精离开前一定仍在花园内,花园内的玫瑰花精始终不会超过n。
Output
对于所有操作1,输出一个整数,表示该花精的玫瑰花编号。
分析:可以考虑线段树。首先我们对区间[1..n]建立一棵线段树。对于每一个节点,
维护4 个值。分别是l,r,mid,p。l 表示在当前结点线段树所在区间最左边的花精
所在的位置,r 表示最右边的花精所在的位置。mid 表示在这个小区间[l,r]中的
两只花精之间的最长距离除以2 后的值。p 表示取mid 值时所在的紧邻的两只
花精的中间位置,也就是在[l,r]中的答案值。
对于1 询问:访问线段树的第一个节点,我们比较l-1,n-r,mid 的值哪
个更大,就选哪个,它们的答案依次是1,n,p。假设我们求得的位置是fairy[x]。
然后访问[fairy[x],fairy[x]]所在的线段树的叶子节点,初始化它的值,然后回溯,
进行合并。对于tr[x].l 与tr[x].r 可以通过两个儿子的l,r 信息得出。对于tr[x].mid
值,首先在左右儿子的mid 值中去一个最大的值。其次考虑一种情况,就是夹在
两个线段之间的距离,可以通过(tr[x+x+1].l-tr[x+x].r) / 2 的值得出在于mid
进行比较,然后p 就随着mid 的值的更新而更新。
对于2 询问:访问询问花精所在的位置,直接将它的叶子节点
[fairy[x],fairy[x]]删除,然后回溯时,再做一次合并操作。
代码
#include <cstdio>
#define maxn 1000005
using namespace std;
struct tnode
{
int l,r,mid,p;
}tree[maxn];
int n,m,a[maxn];
void update(int x)
{
if (tree[x*2].l>0) tree[x].l=tree[x*2].l;
else tree[x].l=tree[x*2+1].l;
if (tree[x*2+1].r>0) tree[x].r=tree[x*2+1].r;
else tree[x].r=tree[x*2].r;
tree[x].p=tree[x*2].p;
tree[x].mid=tree[x*2].mid;
if (tree[x*2].r>0&&tree[x*2+1].l>0)
{
int o=(tree[x*2+1].l-tree[x*2].r)/2;
if (o>tree[x].mid)
{
tree[x].mid=o;
tree[x].p=(tree[x*2+1].l+tree[x*2].r)/2;
}
if (tree[x*2+1].mid>tree[x].mid)
{
tree[x].mid=tree[x*2+1].mid;
tree[x].p=tree[x*2+1].p;
}
}
}
void change(int h,int l,int r,int x,int k)
{
if (l==r)
{
if (k==2)
{
tree[h].l=0;
tree[h].r=0;
tree[h].p=0;
tree[h].mid=0;
}
else
{
tree[h].l=l;
tree[h].r=r;
tree[h].p=0;
tree[h].mid=0;
}
return;
}
int mid=(l+r)/2;
if (x<=mid) change(h*2,l,mid,x,k);
else change(h*2+1,mid+1,r,x,k);
update(h);
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
int c,x;
scanf("%d%d",&c,&x);
if (c==1)
{
if (tree[1].l==0) a[x]=1;
else
{
int sum=-1226789;
if (tree[1].l-1>sum)
{
sum=tree[1].l-1;
a[x]=1;
}
if (tree[1].mid>sum)
{
sum=tree[1].mid;
a[x]=tree[1].p;
}
if (n-tree[1].r>sum)
{
sum=n-tree[1].r;
a[x]=n;
}
}
printf("%d\n",a[x]);
change(1,1,n,a[x],1);
}
else change(1,1,n,a[x],2);
}
}