eg.1 uva_11983
核心有三点:横坐标扫描线+纵坐标离散化+自定义线段树懒操作
首先自定义线段树以及其懒操作,方法非常值得借鉴,题目提示了k<=10,当时也注意到了,但是还是停留在最初级的线段树思维,并没有充分利用。
回忆一下:
struct node
{
int l,r,add;
int mid()
{
return (l+r)/2;
}
ll a[11];
} t[24*maxn];
1,add无疑是lag操作。2,a[i]=j,表示当前l到r内数值为i的有j个。
更重要的 整个线段树中并没有push_down函数,却有push_up函数,并且并不像区间求和那样一句话结束。
回忆一下:
void push_up(int id)
{
memset(t[id].a,0,sizeof(t[id].a));
if(t[id].l==t[id].r)
{
int x=t[id].l;
t[id].a[min(k,t[id].add)]=np[x]-np[x-1];
}
else
{
for(int i=0; i<=k; i++)
{
int cmp=min(k,i+t[id].add);
t[id].a[cmp]+=t[LL(id)].a[i]+t[RR(id)].a[i];
}
}
}
else后好理解,else前是统计离散化前的节点个数(np)。非常值得借鉴。
接下来是纵坐标离散化,与其说是离散化,不如说是将线段分割成点。一般这种情况可以将每个点分割成三个。
其实更好的是:从第一个点到最后倒数第二个点建树,后面的点和当前的点做差,这样五条线段就会变成
(l-1,l-1)(l,l)(l+1,r-2) (r-1,r-1)(r,r)其实和上面是一样的= =也可以写成上面那种形式
但是上面的话第一个是没用的,如果是下面的话就是最后一个是没用的(没有写出来(r+1,+无穷))。下面的话只需要m--就可以了。
最后是扫描线。
for(int i=0;i<2*n-1;i++)
{
update(c[i].l,c[i].r,c[i].v,1);
ans+=t[1].a[k]*(c[i+1].pos-c[i].pos);
}
每次更新完之后看后一条线段的位置,而不能看前一条线段的位置。如果看前面的位置的话就会比较麻烦。(自行想象)。相反,如果看后面的位置的话就会发现很简单,不用过多的修饰。(get)
eg2. hdu_3436
好像有splay(伸展树)的写法,但是对于我这种蒟蒻还不知道伸展树的情况,只会用树状数组慢慢搞出来。QAQ
核心:离散化+(树状数组+二分)+预存。
“预存”:每次将某个元素置顶的时候,我们都需要将其抽出来,放到数组的最前面,那么如果我们如果提前知道了置顶的操作数,我们可以初始的时候将所有数往后挪“操作数”个。同时用树状数组来维护前k个位置有多少个数。
int pos[maxn];映射后第i个人在树状数组上的位置j
int who[2*maxn];第i个位置是第j人(无映射)
单独讲几个操作:
置顶:由于离散化的关系,我们可以知道那个人在数组中的位置,然后把他前面的几个人的位置都+1
update(pos[x],-1);
who[pos[x]]--;
who[k]=p[x];
pos[x]=k;
update(k,1);
k--;
这里who要减1 并不会出现问题,因为树状数组的关系,当他非法的时候树状数组的值是0,巧妙。
询问排名是谁:
树状数组套二分,找到第一个前缀和不小于该排名的节点。那么所找的排名必定前一个节点的前缀和到这个节点的前缀和,那么知道该节点当前的排名只需要再做一些处理就可以了。
询问谁是排名:
由于都离散化过只需要知道该节点在树状数组的位置,树状数组询问一遍该位置的前缀和就行了。