*题解:P3960 [NOIP 2017 提高组] 列队
解析
考虑 \(x = 1\) 怎么做,可以发现此时只有第 \(1\) 列和第 \(m\) 行会发生变动,将其拼起来可以视作一个数列,操作就是单点删除和结尾插入。怎么维护呢?不一定要平衡树,有一种用树状数组的做法:维护每个位置上的数的存在情况,1 / 0 表示这个位置上 有 / 没有 数。这样,要找第 \(k\) 个元素,就相当于找使得前缀和 \(\ge k\) 的最小下标 \(i\),我们借助另一个数组 \(a\) 存储下标对应的元素,就可以求得元素值;删除操作变为置 \(0\);加入操作变为置 1 的同时在数组中添加对应元素。
对于其余情况,我们需要进一步观察性质。\(n\cdot m\) 很大,我们不可能维护整个方阵,但是 \(q\) 次询问对应着 \(q\) 个元素,我们能否只维护因为询问而变动的元素呢?我们依旧遵循先前转化为 0/1 串的思想,考虑每一行的前 \(m - 1\) 个元素与最后一列(把最后一列单独摘出来方便处理,不然向前看齐后会导致对应不上之前的行)删除位于 \((x,y)\) 的元素就意味着在第 \(x\) 行中,第 \(y\) 个元素对应位置置 \(0\),新增元素 \((x,m)\),对应位置置 \(1\),在第 \(m\) 列中,第 \(x\) 个元素对应位置置 \(0\),新增元素 \((x,y)\),对应位置置 1。
可以发现,最多新增元素数量总和不超过 \(2q\),于是我们可以只维护新增元素。那么非新增元素如何处理?由于对于非新增元素而言,每行相对独立,其余行的操作影响不到这一行,所以可以把询问离线下来一行一行处理。
时间复杂度 \(O(q\log^2n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 3e5 + 5,M = 500 * 500,mod = 998244353;
struct Q{
int x,y,id;
friend bool operator < (Q a,Q b){
return a.x != b.x ? a.x < b.x : a.id < b.id;
}
}qu[N],q2[N];
int b[N * 3],cnt[N],cntq[N],pre[N];
vector<ll> npos[N];//用于存储下标对应元素
ll res[N];
void add(int x,int k){
for(;x < N * 3;x += x & -x){
b[x] += k;
}
}
int ask(int x){
int res = 0;
for(;x;x -= x & -x){
res += b[x];
}
return res;
}
int find(int x,int k){//查找第 i 行的第 k 个新增元素,第 n + 1 行是第 m 列
int pr = ask(pre[x - 1]);
int l = pre[x - 1] + 1,r = pre[x];
while(l < r){
int mid = l + r >> 1;
if(ask(mid) - pr >= k) r = mid;
else l = mid + 1;
}
return l - pre[x - 1];
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n,m,q;
cin>>n>>m>>q;
for(int i=1;i<=q;i++){
cin>>qu[i].x>>qu[i].y;
qu[i].id = i;
q2[i] = qu[i];
}
sort(q2 + 1,q2 + q + 1);
for(int i=1;i<=n;i++){
cnt[i] = m - 1;//第 i 行中剩余非新增元素个数
}
vector<int> v;
for(int i=1;i<=m - 1;i++){
add(i,1);
}
for(int i=1;i<=q;i++){
int x = q2[i].x,y = q2[i].y,id = q2[i].id;
if(y < m) cntq[x]++;//便于在树状数组中分配位置
if(q2[i].x != q2[i - 1].x){
for(int j : v){
add(j,1);
}
v.clear();
}
if(y > cnt[x]) continue;
int l = 1,r = m - 1;
while(l < r){
int mid = l + r >> 1;
if(ask(mid) >= y) r = mid;
else l = mid + 1;
}
cnt[x]--;
add(l,-1);
v.push_back(l);
res[id] = 1ll * (x - 1) * m + l;
}
memset(b,0,sizeof(b));
cntq[n + 1] = n + q;//每次询问必定导致在最后一列尾插,加上初始的 n 个
for(int i=1;i<=n + 1;i++){
pre[i] = pre[i - 1] + cntq[i];//第 i 行分配到的起始下标为 (pre[i - 1],pre[i]]
cnt[i] = m - 1;
}
for(int i=1;i<=n;i++){
add(pre[n] + i,1);
npos[n + 1].push_back(1ll * i * m);
}
for(int i=1;i<=q;i++){
int x = qu[i].x,y = qu[i].y,id = qu[i].id;
if(y <= cnt[x]){
cnt[x]--;
int t = find(n + 1,x);
npos[x].push_back(npos[n + 1][t - 1]);
npos[n + 1].push_back(res[id]);
add(pre[x - 1] + npos[x].size(),1);
add(pre[n] + t,-1);
add(pre[n] + n + i,1);
}else if(y == m){
int t = find(n + 1,x);
npos[n + 1].push_back(npos[n + 1][t - 1]);
add(pre[n] + t,-1);
add(pre[n] + n + i,1);
res[id] = npos[n + 1][t - 1];
}else{
int k = find(x,y - cnt[x]),t = find(n + 1,x);//要找的是第 x 行第 y 个元素,但是 vector 只存了新增的元素,所以要减去非新增元素个数
npos[x].push_back(npos[n + 1][t - 1]);
npos[n + 1].push_back(npos[x][k - 1]);
add(pre[x - 1] + k,-1);
add(pre[n] + t,-1);
add(pre[x - 1] + npos[x].size(),1);
add(pre[n] + n + i,1);
res[id] = npos[x][k - 1];
}
cout<<res[id]<<'\n';
}
return 0;
}

浙公网安备 33010602011771号