法1 Splay
题意:对于每个 ( x , y ) (x,y) (x,y),把第x行第y个踢掉,把第x行最后一个移到第x行里面,把踢掉的数插到最后一列的最后一个
十分容易想到一个思路:1到n行每行建一棵splay,维护1~m-1的kth,再按最后一列建一棵
s
p
l
a
y
splay
splay,维护最后一列的kth
那么做法显而易见:
- 把最后一列的 k t h ( x ) kth(x) kth(x)插到第x行的末尾
- 找到第x行的 k t h ( y ) kth(y) kth(y),踢掉并输出
- 把踢掉的数插到最后一列末尾
分析一下复杂度,发现空间是 O ( n 2 ) O(n^2) O(n2)的
想优化
可以把一段区间合并成一个点
每个点代表一个区间,初始时每行都只有一个点,代表区间便为
[
(
i
−
1
)
∗
m
+
1
,
i
∗
m
)
[(i-1)*m+1,i*m)
[(i−1)∗m+1,i∗m),注意是左闭右开
最后一列的话只能一个一个插进去了
每次踢出一个数,把包含这个数的点,分成三个(也有可能不到三个),假如区间是 [ l , r ) [l,r) [l,r),那么把区间分裂成 [ l , k ) , k , [ k + 1 , r ) [l,k) ,{k},[k+1, r) [l,k),k,[k+1,r),然后把k的这个点删掉
这样就把空间优化了
Code:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#define maxn 3000010
#define LL long long
using namespace std;
int n, m, M, sz;
int size[maxn], son[maxn][2], f[maxn];
LL l[maxn], r[maxn];
inline int read(){
int s = 0, w = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
return s * w;
}
struct Splay{
int rt;
int addnode(LL ll, LL rr){
++sz;
l[sz] = ll, r[sz] = rr, size[sz] = r[sz] - l[sz];
f[sz] = son[sz][0] = son[sz][1] = 0;
return sz;
}
void init(LL ll, LL rr){
rt = addnode(ll, rr);
}
int get(int x){ return son[f[x]][1] == x; }
void pushup(int x){ size[x] = size[son[x][0]] + size[son[x][1]] + r[x] - l[x]; }
void connect(int x, int y, int z){
f[x] = y, son[y][z] = x;
}
void rotate(int x){
int fa = f[x], ffa = f[fa], m = get(x), n = get(fa);
connect(son[x][m ^ 1], fa, m);
connect(fa, x, m ^ 1);
connect(x, ffa, n);
pushup(fa); pushup(x);
}
void splay(int x, int goal){
while (f[x] != goal){
int fa = f[x];
if (f[fa] != goal) rotate(get(x) == get(fa) ? fa : x);
rotate(x);
}
if (!goal) rt = x;
}
int split(int x, LL k){
k += l[x];
int o = addnode(k, r[x]);
r[x] = k;
if (!son[x][1]) connect(o, x, 1); else{
int now = son[x][1];
while (son[now][0]) now = son[now][0];
connect(o, now, 0);
while (now != x) pushup(now), now = f[now];
}
splay(o, 0);
return o;
}
LL popkth(int k){
int now = rt;
while (1){
if (size[son[now][0]] >= k) now = son[now][0]; else{
k -= size[son[now][0]];
if (k <= r[now] - l[now]){
if (k < r[now] - l[now]) split(now, k);
if (k > 1) now = split(now, k - 1);
break;
}
k -= r[now] - l[now], now = son[now][1];
}
}
splay(now, 0);
f[son[now][0]] = f[son[now][1]] = 0;
if (!son[now][0]) rt = son[now][1]; else
if (!son[now][1]) rt = son[now][0]; else{
int o = son[now][0];
while (son[o][1]) o = son[o][1];
splay(o, 0);
connect(son[now][1], o, 1);
pushup(o);
}
return l[now];
}
void insert(LL x){
int o = addnode(x, x + 1);
if (!rt) rt = addnode(x, x + 1); else{
int now = rt;
while (son[now][1]) now = son[now][1];
connect(o, now, 1);
pushup(o); splay(o, 0);
}
}
}s[maxn];
int main(){
n = read(), m = read(), M = read();
for (LL i = 1; i <= n; ++i) s[i].init((i - 1) * m + 1, i * m);
s[0].init(m, m + 1);
for (LL i = 2; i <= n; ++i) s[0].insert(i * m);
while (M--){
int x = read(), y = read();
s[x].insert(s[0].popkth(x));
LL ans = s[x].popkth(y);
printf("%lld\n", ans);
s[0].insert(ans);
}
return 0;
}
法2 线段树
这里用到的是权值线段树
因为要解决空间问题,所以可以用权值线段树动态维护队列的情况
开
n
+
1
n+1
n+1个线段树分别维护最后一列和去掉最后一列的
n
n
n行
每次暴力取出答案,在最后一列的末尾加上
取出最后一列对应行的那个数,在那一行的末尾加上
Code:
#include <bits/stdc++.h>
#define maxn 1000010
#define maxm 10000010
#define LL long long
using namespace std;
struct Seg{
int size;
LL val;
}seg[maxm];
int ls[maxm], rs[maxm], n, m, Q, cnt[maxn], now, rt[maxn], lim, size;
inline int read(){
int s = 0, w = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
return s * w;
}
int getsize(int l, int r){ return now > n ? (l <= n ? (r <= n ? r - l + 1 : n - l + 1) : 0) : (l < m ? (r < m ? r - l + 1 : m - l) : 0); }
LL query(int &id, int x, int l, int r){
if (!id){
seg[id = ++size].size = getsize(l, r);
if (l == r)
if (now <= n) seg[id].val = 1LL * (now - 1) * m + 1LL * l;
else seg[id].val = 1LL * l * m;
}
--seg[id].size;
int mid = (l + r) >> 1;
if (l == r) return seg[id].val;
if (!ls[id] && x <= mid - l + 1 || x <= seg[ls[id]].size) return query(ls[id], x, l, mid);
else return query(rs[id], ls[id] ? x - seg[ls[id]].size : x - (mid - l + 1), mid + 1, r);
}
void update(int &id, int x, LL y, int l, int r){
if (!id){
seg[id = ++size].size = getsize(l, r);
if (l == r) seg[id].val = y;
}
++seg[id].size;
if (l == r) return;
int mid = (l + r) >> 1;
if (x <= mid) update(ls[id], x, y, l, mid);
else update(rs[id], x, y, mid + 1, r);
}
int main(){
n = read(), m = read(), Q = read(), lim = max(n, m) + Q;
while (Q--){
int x = read(), y = read();
LL z;
now = y < m ? x : n + 1;
printf("%lld\n", z = query(rt[now], y < m ? y : x, 1, lim));
++cnt[now = n + 1];
update(rt[now], n + cnt[now], z, 1, lim);
if (y < m){
z = query(rt[now], x, 1, lim);
++cnt[x];
update(rt[now = x], m - 1 + cnt[x], z, 1, lim);
}
}
return 0;
}

博客围绕一道题给出两种解法。法1用Splay,每行建一棵Splay维护kth,最后一列也建一棵,原做法空间复杂度为O(n2),可将区间合并成点优化空间;法2用权值线段树,开n+1个线段树分别维护最后一列和n行,动态维护队列情况。
253

被折叠的 条评论
为什么被折叠?



