CF38G Queue(splay)
题目链接
https://codeforces.com/contest/38/problem/G
题意
有n个人依次排队,每个人都有两个属性值 a[i] 、c[i] ,a[i]是重要性值,数值越大越重要,c[i]是良心值。假如前i-1人已经排好队后,第i个人来排队,初始时他在队尾,如果他的a[i]大于排在他前面那位的重要性值,那么两人可以交换位置,每次交换良心值减1,直到他前面的人的重要性值大于a[i]或者良心值为0的时候(即最多交换c[i]次),问最终n个人的队列次序。
思路
我们发现第\(i\)个人的位置只能在\(i-c_i,i-c_i+1,...i\)这些位置。如果\(i>c_i+1\)的话,\(1,2,...i-c_i+1\)这些位置是不能通过交换到达的。
所以,我们可以建一棵区间\(splay\),节点间的关系不是依靠值而是依靠位置,即每个节点构成的子树代表了这些人构成的一段区间。同时在每个节点上记录原先位置和区间最大值,方便我们接下来的操作。
每次操作时,若\(i>c_i+1\),把前\(i-c_i-2\)个数拉到根的左子树里,第\(i-c_i-1\)个数作为根,在根的右子树里插入第\(i\)个人。插入时,判断当前这个人的\(a_i\)是否大于右子树的最大值以及当前递归到的节点,如果是就往左子树递归(说明这个人可以和右子树对应区间的人全部交换而到达左子树),否则就往右子树递归。
如果\(i<=c_i+1\),则表明当前这个人可以与\(splay\)内任何一个人交换,直接从根开始递归。
每次操作完之后将新插入的节点\(splay\)到根,保证\(splay\)的复杂度。
#include<bits/stdc++.h>
using namespace std;
const int maxx = 1e5+10;
int ch[maxx][2],fa[maxx],siz[maxx],ma[maxx],val[maxx];
//ma维护子树即区间的最大值,val保存节点在序列原先的位置
int rt,sz;
int a[maxx],c[maxx];
int get(int x)
{
return ch[fa[x]][1]==x;
}
void update(int x)
{
siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;
ma[x]=max(a[val[x]],max(ma[ch[x][0]],ma[ch[x][1]]));
}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=get(x);
ch[y][k]=ch[x][k^1];fa[ch[y][k]]=y;
ch[x][k^1]=y;fa[y]=x;fa[x]=z;
if(z)ch[z][ch[z][1]==y]=x;
update(y);update(x);
}
void splay(int x,int goal)
{
for(int y;(y=fa[x])!=goal;rotate(x))
if(fa[y]!=goal)rotate((get(x)==get(y))?y:x);
if(goal==0)rt=x;
}
int findkth(int k)
{
int x=rt;
while(1)
{
if(k<=siz[ch[x][0]])x=ch[x][0];
else
{
k-=siz[ch[x][0]]+1;
if(!k)return x;
x=ch[x][1];
}
}
}
void Insert(int &x,int f,int id)
{
if(!x)
{
x=++sz;
fa[x]=f;siz[x]=1;
ma[x]=a[id];val[x]=id;
return;
}
if(a[id]>ma[ch[x][1]]&&a[id]>a[val[x]])Insert(ch[x][0],x,id);
else Insert(ch[x][1],x,id);
update(x);
}
void dfs(int x)
{
if(ch[x][0])dfs(ch[x][0]);
printf("%d ",val[x]);
if(ch[x][1])dfs(ch[x][1]);
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i],&c[i]);
for(int i=1;i<=n;i++)
{
if(i-c[i]<=1)Insert(rt,0,i);
else
{
int k=findkth(i-c[i]-1);
splay(k,0);
Insert(ch[rt][1],rt,i);
}
splay(sz,0);
}
dfs(rt);
return 0;
}