洛谷P3960 列队(动态开点权值线段树)

我们考虑每个人离队后对队列的影响,我们可以得出结论,只会对当前行和最后一行产生影响,我们先看在 m m m列的特殊情况。

( i , m ) (i,m) (i,m)的人从队伍中离开,跑到了最后一位,我们假设后面的人不往上补充,而是往后多了一个人,这时变成了 ( n + 1 , m ) (n+1,m) (n+1,m),我们把他放进 v e c t o r vector vector来维护这个序列,这样每次访问只需要 p o s − n pos-n posn即可

但我们如何找到需要的 p o s pos pos呢,权值线段树登场现学

我们维护一个 s i z e size size数组,储存这个区域少了几个人,这样我们要找第 x x x个人,只需要比较 m i d − l + 1 mid-l+1 midl+1 s i z e [ l e f t s o n ] size[leftson] size[leftson]的大小,就可以判断是
在 左 儿 子 找 x 位 在左儿子找x位 x
或 在 右 儿 子 找 第 x − ( m i d − l + 1 − s i z e [ l e f t s o n ] ) 位 或在右儿子找第x-(mid-l+1-size[leftson])位 xmidl+1size[leftson]
得出的 p o s pos pos返回即可

这时得出的 p o s pos pos,如果在 [ 1 , n ] [1,n] [1,n]中,就可以直接通过编号法则得出编号,但如果大于 n n n说明是后续入队的,那么他的编号已经在 v e c t o r vector vector中存了,直接输出 g [ n + 1 ] [ p o s − n − 1 ] g[n+1][pos-n-1] g[n+1][posn1]即可。

注意:这里的寻找边界并非 [ 1 , n ] [1,n] [1,n],而是 [ 1 , m a x ( n , m ) + Q ] [1,max(n,m)+Q] [1,max(n,m)+Q]

思考一下,极限数据下,我们会做出什么畜生事情,执行q次1 m
那么会出现什么情况呢,每一次查询都会在 v e c t o r vector vector里放进去一个数,总共 Q Q Q次,所以我们查询的区间要满足这种极限情况就必须改为 [ 1 , m a x ( n , m ) + Q ] [1,max(n,m)+Q] [1,max(n,m)+Q]

于是,一个愉快的同理可得,行也解决了
那么思路就出来了,建立n+1棵权值线段树,n棵维护每一行[1,m-1],最后一棵维护最后一列即可
但是会 M L E R E W A MLE_{RE_{WA}} MLEREWA,那么动态开点就可以了,代码并不难写

可能讲的不太好懂
我们模拟一下上面的lsl畜生过程就好了

这是初始情况,为了好看点就横过来了丑陋Markdown警告
[ 1 2 3 4 − − − − ] \left[ \begin{matrix} 1 & 2 & 3 & 4 &-&-&-&- \end{matrix} \right] [1234]

第一次删除(1,2)之后
[ 1 − 3 4 2 − − − ] \left[ \begin{matrix} 1 & - & 3 & 4 &2&-&-&- \end{matrix} \right] [1342]

紧接下来假如我们还要要删(1,2)呢
我们来看此时的size数组是啥样的
s i z e [ 1 ] 1 size[1]_1 size[1]1
s i z e [ 2 ] 1 — — — — — — — — — — — — — — — s i z e [ 3 ] 0 size[2]_1 ———————————————size[3]_0 size[2]1size[3]0
s i z e [ 4 ] 1 — — — — — s i z e [ 5 ] 0 — — — — — — — s i z e [ 6 ] 0 — — — — — — s i z e [ 7 ] 0 size[4]_1 —————size[5]_0———————size[6]_0 ——————size[7]_0 size[4]1size[5]0size[6]0size[7]0
s i z e [ 8 ] 0 − s i z e [ 9 ] 1 − s i z e [ 10 ] 0 − s i z e [ 11 ] 0 − s i z e [ 12 ] 0 − s i z e [ 13 ] 0 − s i z e [ 14 ] 0 − s i z e [ 15 ] 0 size[8]_0 -size[9]_1-size[10]_0 -size[11]_0-size[12]_0 -size[13]_0-size[14]_0 -size[15]_0 size[8]0size[9]1size[10]0size[11]0size[12]0size[13]0size[14]0size[15]0
可以看出,我们一直跑到 s i z e [ 2 ] size[2] size[2]的时候就会面临第一次选择,我们要找的仍然是2,但是左儿子的大小减去 s i z e [ 4 ] size[4] size[4]却只有1了,此时就会往右儿子找 2 − s i z e [ 4 ] = 1 2-size[4]=1 2size[4]=1了,最后进入 s i z e [ 10 ] size[10] size[10]发现 l = r = 3 l=r=3 l=r=3了, 3 3 3就是我们要求的位置了;回到上面看一下,没错,可以输出了。

那么再删两次(1,2)呢,会发现第四次删除在原树中已经找不到了,没关系,别忘了 1 − > m a x l e n 1->maxlen 1>maxlen
看看 s i z e size size数组
s i z e [ 1 ] 3 size[1]_3 size[1]3
s i z e [ 2 ] 3 — — — — — — — — — — — — — — — s i z e [ 3 ] 0 size[2]_3 ———————————————size[3]_0 size[2]3size[3]0
s i z e [ 4 ] 1 — — — — — s i z e [ 5 ] 2 — — — — — — — s i z e [ 6 ] 0 — — — — — — s i z e [ 7 ] 0 size[4]_1 —————size[5]_2———————size[6]_0 ——————size[7]_0 size[4]1size[5]2size[6]0size[7]0
s i z e [ 8 ] 0 − s i z e [ 9 ] 1 − s i z e [ 10 ] 1 − s i z e [ 11 ] 1 − s i z e [ 12 ] 0 − s i z e [ 13 ] 0 − s i z e [ 14 ] 0 − s i z e [ 15 ] 0 size[8]_0 -size[9]_1-size[10]_1 -size[11]_1-size[12]_0 -size[13]_0-size[14]_0 -size[15]_0 size[8]0size[9]1size[10]1size[11]1size[12]0size[13]0size[14]0size[15]0
这次在 s i z e [ 1 ] size[1] size[1]的时候, m i d − l + 1 = 4 mid-l+1=4 midl+1=4又因为 4 − s i z e [ 2 ] &lt; 2 4-size[2]&lt;2 4size[2]<2 就直接奔向 s i z e [ 3 ] size[3] size[3]找第一位了,于是返回的 p o s pos pos为5,大于4了,怎么办,不慌磕瓶药,直接查询vector里的第 p o s − 4 pos-4 pos4位即可

所以总的算法流程结束了,动态开点啥的对着代码感性理解一下就好了
应该算讲清楚了吧…

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring> 
#include<vector>
using namespace std;
const int maxn = 300007;
const int maxm = 10000007;
typedef long long ll;
int maxlen,n,m,q,rt[maxn],lc[maxm],rc[maxm],size[maxm];
vector<ll>g[maxn];
int cnt;
int query(int l,int r,int now,int x)
{
	if(l==r)return l;
	int mid=l+r>>1,h=mid-l+1-size[lc[now]];
	if(x<=h)return query(l,mid,lc[now],x);
	else return query(mid+1,r,rc[now],x-h);
}

void del(int l,int r,int &now,int x)
{	
	if(!now)now=++cnt;
	size[now]++;
	if(l==r)return;
	int mid=l+r>>1;
	if(x<=mid)del(l,mid,lc[now],x);
	else del(mid+1,r,rc[now],x);
}

ll del_line(int x,int y)
{
	int pos=query(1,maxlen,rt[x],y);
	del(1,maxlen,rt[x],pos);
	if(pos<m)
	{
		return 1ll*(x-1)*m+pos;
	}
	else return g[x][pos-m];
}
ll del_row(int x)
{
	int pos=query(1,maxlen,rt[n+1],x);
	del(1,maxlen,rt[n+1],pos);
	if(pos<=n)
	{
		return 1ll*pos*m;
	}
	else return g[n+1][pos-n-1];
}


int main()
{
	scanf("%d%d%d",&n,&m,&q);
	maxlen=max(n,m)+q+1;
	for(int i=1;i<=q;i++)
    {
    	int x,y;
        scanf("%d%d",&x,&y);
        if(y==m)
        {
            ll hang=del_row(x);
            g[n+1].push_back(hang);
            printf("%lld\n",hang);
        }
        else
        {
            ll lie=del_line(x,y);
            g[n+1].push_back(lie);
            ll hang=del_row(x);
            g[x].push_back(hang);
            printf("%lld\n",lie);
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值