NOIP2017 D2T3 列队

列队

题目背景:

NOIP2017 D2T3

分析:平衡树 or 线段树 or 树状数组

 

考场上因为自己不会实现,所以没有过掉这道题,拿了80的暴力,50分的暴力,和30分的平衡树,50分因为询问次数较少,可以直接提取出对应影响到的行,和最后一列,然后直接暴力维护即可,然后对于30分的只询问第一行,相当于,只会影响到第一行和最后一列,直接将两者接到一起,用平衡树暴力维护删除中间一个数,放到最后就可以了。

 

Source

/*
	created by scarlyw
*/
#include <cstdio>
#include <string>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <cmath>
#include <iostream>
#include <queue>
#include <set>
#include <vector>

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<class T>
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<class T>
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,所以直接开5MAXN节点个数动态分配就可以了。时间复杂度O(nlogn),空间复杂度O(n)

Source

/*
    created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>

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<class T>
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<class T>
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<class T>
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 + 1vector,表示后加入的节点,如果是原来没有的,我们可以根据位置,直接在vector中找到对应的节点,显然vector的总大小是O(q)的,空间不会有问题。线段树的空间,考虑和平衡树一样,动态开点,在访问到的时候再分配,每一次最多增加log个结点,所以时空复杂度都是O(nlogn)

 

Source

/*
    created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>

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<class T>
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<class T>
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<class T>
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<long long> 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 + 1vector即可,时间复杂度O(nlogn),空间复杂度O(n)

 

Source

/*
    created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>

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<class T>
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<class T>
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<class T>
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<node> query[MAXN];
std::vector<long long> 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;
}

总的而言,三种实现方法中,平衡树是最为直接的,线段树是最为好写的,树状数组是效率最高的,都有优势。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
你好!感谢你的提问。根据你的要求,我将为你解答关于NOIP2017普及t3问题的内容。 在这道题目中,我们需要解决一个关于棋盘的问题。题目描述了一个n×m的棋盘,每个格子上有一个非负整数。开始时,小明站在左上角的格子上,他可以向右或向下移动一步,每次移动的代价为目标格子上的数值。我们需要找到一条从左上角到右下角的路径,使得移动的总代价最小。 解决这个问题的一个常见的方法是使用动态规划(Dynamic Programming)。我们可以创建一个二维数组dp,其中dp[i][j]表示从起点到达坐标为(i, j)的格子时的最小代价。然后,我们可以按照从左上角到右下角的顺序依次计算dp数组的值。 具体的计算方法如下: 1. 首先,我们可以初始化dp数组的第一行和第一列,即dp[0][j]和dp[i][0],它们表示从起点到达第一行和第一列的格子时的最小代价。初始化的方法是累加前面的格子的代价。 2. 接下来,我们可以使用一个双重循环,从(1, 1)开始遍历整个棋盘。对于每个格子(i, j),我们可以选择从上方格子(i-1, j)或左方格子(i, j-1)中选择一个代价较小的路径,并加上当前格子的代价。即dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]。 3. 最后,当我们计算完dp数组的所有值后,dp[n-1][m-1]即为从起点到达右下角的格子时的最小代价。 这样,我们就可以得到从左上角到右下角的最小代价。希望能对你的问题有所帮助!如果你还有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值