先考虑 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 m−1 个数与最后一列的出入情况(每行与最后一列的进出情况相互影响,无法离线处理)
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;
}
}
总结:本题对于转离线操作压空间还是很妙的,想到思路以后代码实现还是很容易的