P3460 [POI2007]TET-Tetris Attack 题解

题意

有一个栈,其中包含 \(2n\) 个元素,有 \(n\) 个标记,每种元素都有一个标记,对于每一种标记,栈中都有 \(2\) 个。你可以交换相邻两个元素的位置。如果两个相邻元素相同,则将他们删除,删除后,他们上面的元素就会掉下来。问将栈清空使用的最少操作次数以及方案。

解题思路

开一个树状数组,用来维护元素间的距离,如果前面有和目前元素标记相同的元素,就移动两个元素直接的距离减一次,过程中顺便记录移动的位置,如果前面没有,就把当前元素的位置记录下来,以便后续处理,最后的答案就是移动的次数,而方案已经在移动的过程中记录下来了。

自行理解一下吧,还是很好懂的。对于一个标记第一次出现的位置和第二次出现的位置,我们没有必要把第一个往前换或把第二个往后换,尽管这样可能引来一连串的消消乐,但我们直接把第一个往后换或把第二个往前换其实也有同样的效果。

代码实现

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int n, tr[N], ans[N], cnt, vis[N], tot; //cnt用来记录目前已经删除的元素个数,vis数组用来记录前一个与目前元素相同标记的元素的变号 
int lowbit(int x) {return x & -x;} 
void add(int x, int y) //树状数组模板 
{
	while (x <= n)
	{
		tr[x] += y;
		x += lowbit(x);
	}
}
int query(int x)
{
	int res = 0;
	while (x)
	{
		res += tr[x];
		x -= lowbit(x);
	}
	return res;
}
int main()
{
	scanf("%d", &n);
	n <<= 1;
	for (int i = 1; i <= n; i ++)
	{
		int x;
		scanf("%d", &x);
		add(i, 1); //i位置上加了1个元素 
		if (!vis[x]) vis[x] = i;
		else
		{
			int dis = query(i - 1) - query(vis[x]); //dis是元素当前元素与前一个元素之间的距离 
			for (int j = 1; j <= dis; j ++) ans[++ tot] = i - cnt - j; //路上需要交换的点直接计入答案,注意要减去已经删除的点的个数
			add(vis[x], -1), add(i, -1); //消除这两个点 
			cnt += 2; //删除点数加2 
		}
	}
	printf("%d\n", tot);
	for (int i = 1; i <= tot; i ++) printf("%d ", ans[i]); //输出 
	return 0; //完结撒花! 
}

祝大家新年快乐!

posted @ 2023-02-14 17:25  Fislett  阅读(44)  评论(0)    收藏  举报