[NOIP2017 提高组] 列队 题解 树状数组

[NOIP2017 提高组] 列队

先考虑 n = 1 n=1 n=1

  • 此时我们需要的操作:删去第 k k k 个数,在末尾处添加数。

  • 我们可以建一个 01 01 01 序列, 0 0 0 表示不存在, 1 1 1 表示存在,初始全为 1 1 1 。对于事件 ( 1 , k ) (1,k) (1,k) , 该序列中第一个使得前缀大于等于 k k k 的下标所对应的学生编号即为该事件出队学生的编号,将该下标对应的值设为 0 0 0

  • 可用树状数组

// 查询第一个使得前缀大于等于k的下标
int select(int k)
{
    int pos=0, cur=0;
    for(int i=20;i>=0;i--)
    {
        pos+=(1<<i);
        if(pos>N || cur+tree[pos]>=k) pos-=(1<<i);
        else cur+=tree[pos];
    }
    return pos+1;
}

	vector <int> row;
	
	cin>>n>>m>>q;
    N=m+q;	//该01序列至多m+q个元素
    for(int i=1;i<=N;i++)
        add(i,1);   
        
	while(q--)
    {
        cin>>x>>y;
        int pos = select(y);
        add(pos, -1);
        int out;
		if(pos<=m)
			out = pos;
		else
			out = row[pos-m-1];		
		row.push_back(out);
		cout << out << endl;
    }

  • 再看一般情况
    在这里插入图片描述

  • 若现有事件 ( 2 , 3 ) (2,3) (2,3) ,那么 7 7 7 号学生离开 B B B 序列, D D D 序列第 2 2 2 个学生进入 B B B 序列, 7 7 7 号学生进入 D D D 序列

  • 对于事件 ( 2 , 3 ) (2,3) (2,3),只有第二行与最后一列会受其影响。我们需要做的:将第二行序列的第三个去除,将最后一列序列的第二个删去,并将其加入第二行序列末尾,将第二行序列第三个加入最后一列序列的末尾

  • 总操作可概括为:查询序列第 k k k 个数,在末尾处添加数

  • 因此 A , B , C , D A,B,C,D A,B,C,D 序列均可以用上文所述的 01 01 01 序列

  • 然而,我们建立那么多 01 01 01 序列显然会 MLE ,而由于行与行之间互不影响的特性,我们可以按行离线预处理出每一个事件离队的序列下标,在处理完一行后将该树状数组撤销为初始状态


	vector <int> cmd[MAXN];
 	
 	cin>>n>>m>>q;
    N=max(n,m)+q;	//此时至多有 max(n,m)+q 个元素
	for(int i=1;i<=q;i++)
    {
        cin>>a[i]>>b[i];
        if(b[i]!=m)
            cmd[a[i]].push_back(i);
    }
    
    for(int i=1;i<=n;i++)
    {
        for(int qid : cmd[i])
        {
            pre[qid] = select (b[qid]);
            add(pre[qid], -1);
        }
        for(int qid : cmd[i])
        {
            add(pre[qid], 1);
        }
    }
  • 我们再根据事件的顺序在线处理每行 m − 1 m-1 m1 个数与最后一列的出入情况(每行与最后一列的进出情况相互影响,无法离线处理)
	vector <int> row[MAXN],colum;

	// 此时树状数组存的01序列是最后一列的
	for(int i=1;i<=q;i++)
	{
		int pos = select(a[i]);
		add(pos,-1);
		
		int in,out;		//in 表示进入这一行的, out 表示离开这一行的
		
		if(pos<=n)
			in = pos*m;
		else
			in = colum[pos-n-1];
		
		if(b[i]==m)
			out = in;
		else
		{
			row[a[i]].push_back(in);
			if(pre[i]<m)
				out = (a[i]-1)*m+pre[i];
			else
				out = row[a[i]][pre[i]-m];
		}
		colum.push_back(out);
		cout << out << endl;
	}

code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=3e5+5;
int n,m,N,q,a[MAXN],b[MAXN],pre[MAXN],tree[MAXN<<1];
vector <int> cmd[MAXN],row[MAXN],colum;
int lowbit(int x){return x&(-x);}
void add(int x,int delta)
{
	for(int i=x;i<=N;i+=lowbit(i))
		tree[i]+=delta;
}
int select(int k)
{
	int pos=0,cur=0;
	for(int i=20;i>=0;i--)
	{
		pos+=(1<<i);
		if(cur+tree[pos]>=k || pos>N) pos-=(1<<i);
		else cur+=tree[pos];
	}
	return pos+1;
}
signed main()
{
	cin>>n>>m>>q;
	for(int i=1;i<=q;i++)
	{
		cin>>a[i]>>b[i];
		if(b[i]!=m)
			cmd[a[i]].push_back(i);
	}
	
	
	N = max(n,m)+q;
	
	for(int i=1;i<=N;i++)
		add(i,1);
	
	for(int i=1;i<=n;i++)
	{
		for(auto qid : cmd[i])
		{
			pre[qid] = select(b[qid]);
			add(pre[qid],-1);
		}
		for(auto qid : cmd[i])
		{
			add(pre[qid], 1);
		}
	}
	
	for(int i=1;i<=q;i++)
	{
		int pos = select(a[i]);
		add(pos,-1);
		
		int in,out;
		
		if(pos<=n)
			in = pos*m;
		else
			in = colum[pos-n-1];
		
		if(b[i]==m)
			out = in;
		else
		{
			row[a[i]].push_back(in);
			if(pre[i]<m)
				out = (a[i]-1)*m+pre[i];
			else
				out = row[a[i]][pre[i]-m];
		}
		colum.push_back(out);
		cout << out << endl;
	}
}

总结:本题对于转离线操作压空间还是很妙的,想到思路以后代码实现还是很容易的

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值