原题链接
解析
考虑 \(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;
}