列队
题目背景:
NOIP2017 D2T3
分析:平衡树 or 线段树 or 树状数组
考场上因为自己不会实现,所以没有过掉这道题,拿了80的暴力,50分的暴力,和30分的平衡树,50分因为询问次数较少,可以直接提取出对应影响到的行,和最后一列,然后直接暴力维护即可,然后对于30分的只询问第一行,相当于,只会影响到第一行和最后一列,直接将两者接到一起,用平衡树暴力维护删除中间一个数,放到最后就可以了。
Source:
/*
created by scarlyw
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
inline char read() {
static const int IN_LEN = 1024 * 1024;
static char buf[IN_LEN], *s, *t;
if (s == t) {
t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
if (s == t) return -1;
}
return *s++;
}
template
inline void R(T &x) {
static char c;
static bool iosig;
for (c = read(), iosig = false; !isdigit(c); c = read()) {
if (c == -1) return ;
if (c == '-') iosig = true;
}
for (x = 0; isdigit(c); c = read())
x = ((x << 2) + x << 1) + (c ^ '0');
if (iosig) x = -x;
}
const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
*oh++ = c;
}
template
inline void W(T x) {
static int buf[30], cnt;
if (x == 0) write_char('0');
else {
if (x < 0) write_char('-'), x = -x;
for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
while (cnt) write_char(buf[cnt--]);
}
}
inline void flush() {
fwrite(obuf, 1, oh - obuf, stdout);
}
const int MAXQ = 300000 + 10;
int n, m, q, x, y;
struct data {
int x, y;
} query[MAXQ];
inline void solve_small() {
static const int MAXQ = 500 + 10;
static const int MAXN = 50000 + 10;
static long long pos[MAXQ][MAXN], last[MAXN];
static int xx[MAXQ], h[MAXN];
for (int i = 1; i <= q; ++i)
R(query[i].x), R(query[i].y), xx[i] = query[i].x;
std::sort(xx + 1, xx + q + 1);
int cnt = 1;
for (int i = 2; i <= q; ++i) if (xx[i] != xx[i - 1]) xx[++cnt] = xx[i];
for (int i = 1; i <= cnt; ++i) h[xx[i]] = i;
for (int i = 1; i <= cnt; ++i)
for (int j = 1; j < m; ++j)
pos[i][j] = (long long)(xx[i] - 1) * m + j;
for (int i = 1; i <= n; ++i) last[i] = (long long)i * m;
for (int i = 1; i <= q; ++i) {
int x = query[i].x, y = query[i].y, hh = h[x];
long long zz;
if (y != m) zz = pos[hh][y];
else zz = last[x];
W(zz), write_char('\n');
if (y != m) {
for (int j = y; j < m - 1; ++j) pos[hh][j] = pos[hh][j + 1];
pos[hh][m - 1] = last[x];
}
for (int j = x; j < n; ++j) last[j] = last[j + 1];
last[n] = zz;
}
}
struct node *null;
struct node {
node *lc, *rc;
long long val;
int rank, size;
node(long long val = 0) : val(val), rank(rand()), size(1) {
lc = rc = null;
}
inline void maintain() {
size = lc->size + rc->size + 1;
}
} pool[MAXQ << 1], *root;
int pool_top;
inline node *new_node(long long val = 0) {
node *u = &pool[pool_top++];
return *u = node(val), u;
}
inline node *build(long long *a, int n) {
static node *stack[MAXQ << 1], *pre, *u;
int top = 0;
for (int i = 1; i <= n; ++i) {
u = new_node(a[i]), pre = null;
while (top && stack[top]->rank > u->rank)
pre = stack[top], stack[top]->maintain(), stack[top--] = null;
if (top) stack[top]->rc = u;
u->lc = pre, stack[++top] = u;
}
while (top) stack[top--]->maintain();
return stack[1];
}
inline void dfs(node *cur) {
if (cur->lc != null) dfs(cur->lc);
std::cout << cur->val << " ";
if (cur->rc != null) dfs(cur->rc);
}
struct pair {
node *first, *second;
pair(node *first, node *second) : first(first), second(second) {}
pair() {}
} ;
inline pair split(node *u, int k) {
if (u == null) return pair(null, null);
pair t;
if (k <= u->lc->size) t = split(u->lc, k), u->lc = t.second, t.second = u;
else t = split(u->rc, k - u->lc->size - 1), u->rc = t.first, t.first = u;
return u->maintain(), t;
}
inline node *merge(node *u, node *v) {
if (u == null) return v;
if (v == null) return u;
if (u->rank < v->rank) {
u->rc = merge(u->rc, v);
return u->maintain(), u;
}
else {
v->lc = merge(u, v->lc);
return v->maintain(), v;
}
}
inline void modify(int pos) {
pair t1 = split(root, pos - 1), t2 = split(t1.second, 1);
W(t2.first->val), write_char('\n');
root = merge(t1.first, merge(t2.second, t2.first));
}
long long a[MAXQ << 1];
inline void solve_special() {
for (int i = 1; i <= m - 1; ++i) a[i] = i;
for (int i = 1; i <= n; ++i) a[m - 1 + i] = (long long)i * m;
null = new_node(0), null->size = 0, null->lc = null->rc = null;
root = build(a, n + m - 1);
for (int i = 1; i <= q; ++i) R(x), R(y), modify(y);
}
int main() {
freopen("phalanx.in", "r", stdin);
freopen("phalanx.out", "w", stdout);
R(n), R(m), R(q);
if (q <= 500) solve_small();
else solve_special();
flush();
return 0;
}
然后,来讲解下题解,首先,平衡树做法可能是最显然的,直接维护n + 1棵平衡树,n棵表示各行,最后一棵表示最后一列,平衡树的节点都不用开满,如果对于一行,询问了中间的某个位置x,我们可以直接将它拆成[l, x - 1], [x + 1, r],[x, x]三个节点,然后合并前两个,将最后一个放到最后一列,并且从最后一列中,提取出对应位置的元素放入这一行的平衡树最后就可以了。最终的节点数不会唱过q * 4 + m,所以直接开5倍MAXN节点个数动态分配就可以了。时间复杂度O(nlogn),空间复杂度O(n)
Source:
/*
created by scarlyw
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
inline char read() {
static const int IN_LEN = 1024 * 1024;
static char buf[IN_LEN], *s, *t;
if (s == t) {
t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
if (s == t) return -1;
}
return *s++;
}
///*
template
inline void R(T &x) {
static char c;
static bool iosig;
for (c = read(), iosig = false; !isdigit(c); c = read()) {
if (c == -1) return ;
if (c == '-') iosig = true;
}
for (x = 0; isdigit(c); c = read())
x = ((x << 2) + x << 1) + (c ^ '0');
if (iosig) x = -x;
}
//*/
const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
*oh++ = c;
}
template
inline void W(T x) {
static int buf[30], cnt;
if (x == 0) write_char('0');
else {
if (x < 0) write_char('-'), x = -x;
for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
while (cnt) write_char(buf[cnt--]);
}
}
inline void flush() {
fwrite(obuf, 1, oh - obuf, stdout);
}
/*
template
inline void R(T &x) {
static char c;
static bool iosig;
for (c = getchar(), iosig = false; !isdigit(c); c = getchar())
if (c == '-') iosig = true;
for (x = 0; isdigit(c); c = getchar())
x = ((x << 2) + x << 1) + (c ^ '0');
if (iosig) x = -x;
}
//*/
const int MAXN = 300000 + 10;
struct node *null;
struct node {
node *lc, *rc;
int size, rank;
long long l, r;
node() {}
node(long long l, long long r) : l(l), r(r), size(r - l + 1), rank(rand()) {
lc = rc = null;
}
inline void maintain() {
size = lc->size + rc->size + r - l + 1;
}
} pool[MAXN * 5], *root[MAXN], *last;
int pool_top;
inline node* new_node(long long l, long long r) {
if (r - l + 1 == 0) return null;
node *u = &pool[pool_top++];
return *u = node(l, r), u;
}
inline node *build(long long *a, int n) {
static node *stack[MAXN], *u, *pre;
int top = 0;
for (int i = 1; i <= n; ++i) {
u = new_node(a[i], a[i]), pre = null;
while (top && stack[top]->rank > u->rank)
pre = stack[top], stack[top]->maintain(), stack[top--] = null;
if (top) stack[top]->rc = u;
u->lc = pre, stack[++top] = u;
}
while (top) stack[top--]->maintain();
return stack[1];
}
node* merge(node *u, node *v) {
if (u == null) return v;
if (v == null) return u;
if (u->rank < v->rank) {
u->rc = merge(u->rc, v);
return u->maintain(), u;
} else {
v->lc = merge(u, v->lc);
return v->maintain(), v;
}
}
struct pair {
node *first, *second;
pair() {}
pair(node *first, node *second) : first(first), second(second) {}
} ;
pair split_lower(node *u, int k) {
if (u == null) return pair(null, null);
pair t;
if (k < u->lc->size + u->r - u->l + 1)
t = split_lower(u->lc, k), u->lc = t.second, t.second = u;
else t = split_lower(u->rc, k - u->lc->size - u->r + u->l - 1),
u->rc = t.first, t.first = u;
return u->maintain(), t;
}
pair split_upper(node *u, int k) {
if (u == null) return pair(null, null);
pair t;
if (k <= u->lc->size)
t = split_upper(u->lc, k), u->lc = t.second, t.second = u;
else t = split_upper(u->rc, k - u->lc->size - u->r + u->l - 1),
u->rc = t.first, t.first = u;
return u->maintain(), t;
}
int n, m, q, x, y;
long long a[MAXN];
inline void solve() {
null = &pool[pool_top++], null->lc = null->rc = null;
null->l = null->r = null->size = 0, null->rank = rand();
R(n), R(m), R(q);
for (int i = 1; i <= n; ++i) a[i] = (long long)m * i;
last = build(a, n);
for (int i = 1; i <= n; ++i)
root[i] = new_node((long long)(i - 1) * m + 1, (long long)i * m - 1);
while (q--) {
R(x), R(y);
if (y == m) {
pair t1 = split_lower(last, x - 1);
pair t2 = split_lower(t1.second, 1);
W(t2.first->l), write_char('\n');
last = merge(t1.first, merge(t2.second, t2.first));
} else {
pair t1 = split_lower(root[x], y - 1);
pair t2 = split_upper(t1.second, 1);
long long pos = t2.first->l + y - t1.first->size - 1;
long long l = t2.first->l, r = t2.first->r;
W(pos), write_char('\n');
node *d1 = new_node(l, pos - 1);
node *d2 = new_node(pos, pos);
node *d3 = new_node(pos + 1, r);
pair t3 = split_lower(last, x - 1);
pair t4 = split_lower(t3.second, 1);
last = merge(t3.first, merge(t4.second, d2));
root[x] = merge(t1.first, merge(merge(merge(d1, d3),
t2.second), t4.first));
}
}
flush();
}
int main() {
solve();
return 0;
}
然后是线段树的做法,我们用线段树维护,当前区间中已经被用了多少个节点,还是开n + 1棵,表示n行和最后一列,每一次询问哪一个位置是第k个没有使用的位置,如果发现是原来已有的节点,可以直接计算出它的标号,如果是原来没有的节点,我们可以维护n + 1个vector,表示后加入的节点,如果是原来没有的,我们可以根据位置,直接在vector中找到对应的节点,显然vector的总大小是O(q)的,空间不会有问题。线段树的空间,考虑和平衡树一样,动态开点,在访问到的时候再分配,每一次最多增加log个结点,所以时空复杂度都是O(nlogn)
Source:
/*
created by scarlyw
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
inline char read() {
static const int IN_LEN = 1024 * 1024;
static char buf[IN_LEN], *s, *t;
if (s == t) {
t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
if (s == t) return -1;
}
return *s++;
}
///*
template
inline void R(T &x) {
static char c;
static bool iosig;
for (c = read(), iosig = false; !isdigit(c); c = read()) {
if (c == -1) return ;
if (c == '-') iosig = true;
}
for (x = 0; isdigit(c); c = read())
x = ((x << 2) + x << 1) + (c ^ '0');
if (iosig) x = -x;
}
//*/
const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
*oh++ = c;
}
template
inline void W(T x) {
static int buf[30], cnt;
if (x == 0) write_char('0');
else {
if (x < 0) write_char('-'), x = -x;
for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
while (cnt) write_char(buf[cnt--]);
}
}
inline void flush() {
fwrite(obuf, 1, oh - obuf, stdout);
}
/*
template
inline void R(T &x) {
static char c;
static bool iosig;
for (c = getchar(), iosig = false; !isdigit(c); c = getchar())
if (c == '-') iosig = true;
for (x = 0; isdigit(c); c = getchar())
x = ((x << 2) + x << 1) + (c ^ '0');
if (iosig) x = -x;
}
//*/
const int MAXN = 300000 + 10;
struct node {
int left, right, sum;
} tree[MAXN * 20];
int root[MAXN];
int cnt, id, n, m, q, x, y;
std::vector val[MAXN];
inline int query(int &cur, int l, int r, int k) {
if (cur == 0) cur = ++cnt;
if (l == r) return l;
int mid = l + r >> 1, temp;
return ((temp = mid - l + 1 - tree[tree[cur].left].sum) >= k) ?
query(tree[cur].left, l, mid, k)
: query(tree[cur].right, mid + 1, r, k - temp);
}
inline void modify(int &cur, int l, int r, int pos) {
if (cur == 0) cur = ++cnt;
tree[cur].sum++;
if (l == r) return ;
int mid = l + r >> 1;
(pos <= mid) ? modify(tree[cur].left, l, mid, pos)
: modify(tree[cur].right, mid + 1, r, pos);
}
inline long long query_l(int x, long long pos) {
id = query(root[0], 1, n + q, x), modify(root[0], 1, n + q, id);
long long ans = (id <= n) ? ((long long)id * m) : val[0][id - n - 1];
return val[0].push_back(pos ? pos : ans), ans;
}
inline long long query_r(int x, int pos) {
id = query(root[x], 1, m - 1 + q, pos), modify(root[x], 1, m - 1 + q, id);
long long ans = (id < m) ? ((long long)(x - 1) * m + id) : val[x][id - m];
return val[x].push_back(query_l(x, ans)), ans;
}
inline void solve() {
R(n), R(m), R(q);
for (int i = 1; i <= q; ++i) {
R(x), R(y);
W((y != m) ? query_r(x, y) : query_l(x, 0)), write_char('\n');
}
}
int main() {
solve();
flush();
return 0;
}
最后是标算本意要求的树状数组做法,考虑上面的线段树做法,我们可以比较显然的发现,对于某一行而言,对于这一行的询问是与其他行分开的,也就是说,线段树找空位的操作,第x个空位所在当前行的位置,是不会因为询问顺序而改变的,那么我们就可以离线直接分开处理掉每一行的询问,然后再利用这个树状数组作为最后一行,每一次答案和上面一样,维护n + 1个vector即可,时间复杂度O(nlogn),空间复杂度O(n)。
Source:
/*
created by scarlyw
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
inline char read() {
static const int IN_LEN = 1024 * 1024;
static char buf[IN_LEN], *s, *t;
if (s == t) {
t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
if (s == t) return -1;
}
return *s++;
}
///*
template
inline void R(T &x) {
static char c;
static bool iosig;
for (c = read(), iosig = false; !isdigit(c); c = read()) {
if (c == -1) return ;
if (c == '-') iosig = true;
}
for (x = 0; isdigit(c); c = read())
x = ((x << 2) + x << 1) + (c ^ '0');
if (iosig) x = -x;
}
//*/
const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
*oh++ = c;
}
template
inline void W(T x) {
static int buf[30], cnt;
if (x == 0) write_char('0');
else {
if (x < 0) write_char('-'), x = -x;
for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
while (cnt) write_char(buf[cnt--]);
}
}
inline void flush() {
fwrite(obuf, 1, oh - obuf, stdout);
}
/*
template
inline void R(T &x) {
static char c;
static bool iosig;
for (c = getchar(), iosig = false; !isdigit(c); c = getchar())
if (c == '-') iosig = true;
for (x = 0; isdigit(c); c = getchar())
x = ((x << 2) + x << 1) + (c ^ '0');
if (iosig) x = -x;
}
//*/
const int MAXN = 300000 + 10;
int n, m, q, x, y, max, low;
int bit[MAXN << 1 | 1], qx[MAXN], qy[MAXN], cur_id[MAXN];
struct node {
int pos, id;
node(int pos = 0, int id = 0) : pos(pos), id(id) {}
} ;
std::vector query[MAXN];
std::vector val[MAXN];
inline void add(int i, int x) {
for (; i <= max; i += i & -i) bit[i] += x;
}
inline int query_kth(int k) {
int cur = 0;
for (int i = low - 1; i >= 0; --i)
(cur + (1 << i) <= max && bit[cur + (1 << i)] < k) ?
(cur |= (1 << i), k -= bit[cur]) : 0;
return cur + 1;
}
inline void read_in() {
R(n), R(m), R(q);
for (int i = 1; i <= q; ++i) {
R(x), R(y);
(y != m) ? (query[x].push_back(node(y, i)), 0) : (0);
qx[i] = x, qy[i] = y;
}
max = std::max(n, m) + q;
for (int x = 1; x < max; x <<= 1, low++);
}
inline void solve_number() {
for (int i = 1; i <= max; ++i) add(i, 1);
for (int i = 1; i <= n; ++i) {
for (int p = 0; p < query[i].size(); ++p) {
node *cur = &query[i][p];
cur_id[cur->id] = query_kth(cur->pos), add(cur_id[cur->id], -1);
}
for (int p = 0; p < query[i].size(); ++p) {
node *cur = &query[i][p];
add(cur_id[cur->id], 1);
}
}
}
inline void solve_query() {
long long cur_ans;
for (int i = 1; i <= q; ++i) {
int id = query_kth(qx[i]);
cur_ans = (id <= n) ? ((long long)id * m) : val[0][id - n - 1];
add(id, -1);
if (qy[i] != m) {
val[qx[i]].push_back(cur_ans);
cur_ans = (cur_id[i] < m) ? ((long long)(qx[i] - 1) * m + cur_id[i])
: val[qx[i]][cur_id[i] - m];
}
val[0].push_back(cur_ans), W(cur_ans), write_char('\n');
}
}
int main() {
read_in();
solve_number();
solve_query();
flush();
return 0;
}
总的而言,三种实现方法中,平衡树是最为直接的,线段树是最为好写的,树状数组是效率最高的,都有优势。