P3960 列队

终于把去年完全不懂的题做出来了!激动!


这道题可以说是很经典了。即使去年的题,还是经典!

题目叫你维护一个矩阵,每次操作支持删除掉第\(x\)行第\(y\)列的数字,然后自动执行两个动作:

  1. 向左看齐。每个同学如果左边有空位的话就向左移动一位。显然这个操作过后空位会在第\(x\)行第\(m\)列。

  2. 向前看齐。每个同学如果前面有空位的话就向前移动一位。显然这个操作过后空位会在第\(n\)行第\(m\)列。

每次操作后都要输出一个数字,就是被删除的点的编号。


首先当然是暴力模拟走一波。先让数组暴力对齐,然后我们才能想到优化。

发现从第\(x\)行的某一列删除指定的第\(y\)个点的话,第一个操作只会在第\(x\)行中出现!特殊地,如果恰好是第\(m\)个点的话,无需执行操作。

第一个操作过后,当空位在第\(x\)行时,发现第二个操作只会在第\(m\)列中出现!如果恰好是第\(n\)行的话,无需执行操作。

显然我们需要找出某一行中的第\(k\)大,然后删除这个点。用什么数据结构?

链表?删除点是很方便,但是找第\(k\)大显然会超时。

于是我们想到了平衡树。。。

对于元素向前挪向左挪的操作,其实我们只需要使用平衡树,这些操作就不存在了。

存下 每一行除了最后一个 和 最后一列 的元素。最后一列的比较特殊,因为也需要挪。

显然,我们找出第\(k\)大然后记录一下编号再删除,同时从最后一列中找出第\(x\)个,把这个点移动到行的末尾,新点就直接加入到最后一列的末尾。

算法终于介绍完了。

但是:\(n, m \leq 3 \time 10^5\),数据巨大!

解决方法是:一开始能合并的节点就合并,需要用到某个节点就从那个节点中分裂!

请理解这个splitnode函数。

int splitnode(int x, int k)// source[l, l + k - 1], new [l + k, r]
	{
		int y = ++tot;
		l[y] = l[x] + k, r[y] = r[x];
		r[x] = l[x] + k - 1;
		if(ch[x][1] == 0)
		{
			ch[x][1] = y; fa[y] = x;
		}
		else
		{
			int t = ch[x][1];
			while(ch[t][0]) t = ch[t][0];
			ch[t][0] = y; fa[y] = t;
			//while(t != x) pushup(t), t = fa[t];
		}
		splay(y, 0);
		return y;
	}

然后如何删除点?

在splay里面,我们常用的删除点的方法是找他的前驱后继,split出这个区间,然后把它删除掉。

split操作的缺点是你需要两个虚拟节点!

但是显然在这道题中,不可以有虚拟节点(至少我不会写),所以这种方法去掉。

现在给出无split的splay删除操作:

  1. 将待删除的点splay到根,将他左右儿子到它的父亲标记去掉。

  2. 如果根的左儿子为空,则新的根结点就是他的右子树。

  3. 如果根的儿子不为空,那么找到他的前驱,将他splay到根位置,他的右儿子是原根结点的右儿子,父亲标记也做一下,最后pushup即可。

时间关系写不了了,代码:

#include<cstdio>

const int maxn = 3000005;

long long l[maxn], r[maxn], size[maxn], fa[maxn], ch[maxn][2];
int tot;
long long n, m, q;
struct Splay
{
	int root;
	int identify(int x)
	{
		return ch[fa[x]][1] == x;
	}
	void connect(int son, int f, int k)
	{
		fa[son] = f;
		ch[f][k] = son;
	}
	void pushup(int x)
	{
		size[x] = size[ch[x][0]] + size[ch[x][1]] + (r[x] - l[x] + 1);
	}
	void rotate(int x)
	{
		int y = fa[x];
		int z = fa[y];
		int yk = identify(x);
		int zk = identify(y);
		int b = ch[x][yk ^ 1];
		connect(b, y, yk);
		connect(y, x, yk ^ 1);
		connect(x, z, zk);
		pushup(y);
		pushup(x);
	}
	void splay(int x, int goal)
	{
		while(fa[x] != goal)
		{
			int y = fa[x];
			int z = fa[y];
			if(z != goal) identify(x) == identify(y) ? rotate(y) : rotate(x);
			rotate(x);
		}
		if(goal == 0) root = x;
	}
	void insert(long long ll, long long rr)
	{
		int now = root, f = 0;
		while(now)
		{
			f = now;
			now = ch[now][1];
		}
		now = ++tot;
		l[now] = ll, r[now] = rr;
		fa[now] = f; if(f) ch[f][1] = now;
		splay(now, 0);
	}
	int splitnode(int x, int k)// source[l, l + k - 1], new [l + k, r]
	{
		int y = ++tot;
		l[y] = l[x] + k, r[y] = r[x];
		r[x] = l[x] + k - 1;
		if(ch[x][1] == 0)
		{
			ch[x][1] = y; fa[y] = x;
		}
		else
		{
			int t = ch[x][1];
			while(ch[t][0]) t = ch[t][0];
			ch[t][0] = y; fa[y] = t;
			//while(t != x) pushup(t), t = fa[t];
		}
		splay(y, 0);
		return y;
	}
	long long popkth(int k)
	{
		int now = root;
		while(2333)
		{
			int left = ch[now][0];
			if(size[left] + (r[now] - l[now] + 1) < k)
			{
				k -= size[left] + (r[now] - l[now] + 1);
				now = ch[now][1];
			}
			else if(size[left] >= k) now = left;
			else
			{
				k -= size[left];
				if(k != (r[now] - l[now] + 1)) splitnode(now, k);
				if(k != 1) now = splitnode(now, k - 1);
				break;
			}
		}
		splay(now, 0);
		fa[ch[now][0]] = fa[ch[now][1]] = 0;
		if(ch[now][0] == 0) root = ch[now][1];
		else
		{
			int t = ch[now][0];
			while(ch[t][1]) t = ch[t][1];
			splay(t, 0);
			ch[t][1] = ch[now][1];
			fa[ch[t][1]] = t;
			pushup(t);
		}
		return l[now];
	}
	void print(int u)
	{
		if(ch[u][0]) print(ch[u][0]);
		printf("%lld %lld\n", l[u], r[u]);
		if(ch[u][1]) print(ch[u][1]);
	}
} s[300005];
long long read()
{
	long long ans = 0, s = 1;
	char ch = getchar();
	while(ch > '9' || ch < '0')
	{
		if(ch == '-') s = -1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9')
	{
		ans = ans * 10 + ch - '0';
		ch = getchar();
	}
	return s * ans;
}
int main()
{
	n = read(), m = read(), q = read();
	for(long long i = 1; i <= n; i++)
	{
		 s[i].insert((i - 1) * m + 1, i * m - 1);
		 s[0].insert(i * m, i * m);
	}
	/*
	for(int i = 1; i <= n; i++)
	{
		s[i].print(s[i].root);
		printf("\n");
	}
	s[0].print(s[0].root);
	printf("\n");
	return 0;
	*/
	while(q--)
	{
		int x = read(), y = read();
		long long pop1 = s[0].popkth(x);
		s[x].insert(pop1, pop1);
		long long pop2 = s[x].popkth(y);
		s[0].insert(pop2, pop2);
		printf("%lld\n", pop2);
	}
	return 0;
}
posted @ 2018-08-23 14:26  Garen-Wang  阅读(139)  评论(0编辑  收藏  举报