LuoguP7636 题解

题目传送门


\(1)\)\(n\le 100,m\le3500\)

采用直接模拟的方法。由于版本之间需要时时切换,考虑用结构体直接存下一个版本,每次 save 时放入 vector 中,方便 load 时的查找。同时记录当前的版本,做 paint 操作时直接在当前版本上修改即可。

for(int i=1;i<=m;i++)
{
	char s[8];
	scanf("%s",s);
	if(s[0]=='P')paint();
	else if(s[0]=='S')
		his.push_back(now);
	else if(s[0]=='L')
	{
		int x;scanf("%d",&x);
		now=his[x-1];
	}
}

\(2)\)\(n\le 1000,m\le 10^5\)

在画面的一次次修改、保存、回档中,我们发现有些操作是没必要进行的。如样例 \(2\) 中,在进行 LOAD 1 操作后,第二次未保存的 paint 操作便成了无效操作。

但是否在读入过程中就能判断出哪些操作是无效的呢?答案是不能的。考虑下面一系列操作:

PAINT
SAVE
PAINT
SAVE
LOAD 1
PAINT
LOAD 2

在进行 LOAD 1 操作后,虽然回到了第一次存档时的画面,但后面的 LOAD 2 操作回到第二次存档时的画面。而在两次存档之间的操作,虽然在 LOAD 1 时不会用到,但仍对答案造成影响。

因此从前往后扫无法判断出不需要哪些操作,而从后往前扫就能得到有效的染色指令。如上面的例子中,我们先遇到 LOAD 2 然后跳至第二处存档,继续往前扫,最后得出只有第一和第二次染色是有效的。

for(int i=1;i<=m;i++)
{
	qq[i].init();
	if(qq[i].s[0]=='S')
		sav[++cnt]=i;// 记录第cnt次存档对应的位置,便于跳回
}
for(int i=m;i>0;i--)
{
	if(qq[i].s[0]=='S')continue;// 跳过存档处
	if(qq[i].s[0]=='L')i=sav[qq[i].to];
	if(qq[i].s[0]=='P')paint(qq[i]);
}

注意到上述代码直接在从后往前扫时就进行了有效的染色操作。思考一下,如果这些操作从前往后去做,前面染过的颜色可能被后面重新染而改变,仍然需要一个一个去染,如果所有的操作都为 PAINT x 0 0 999 999 ,单次染色的复杂度就高达 \(O(5\times 10^5)\),无法承受。

而从后往前去染色,我们只需要染上没有被染过的区域,从而减少重复染色所带来的巨大复杂度。而如何跳过被染过的区域,到达下一个未染过且需要被染色的区域呢?这里使用并查集来实现这一操作。

用并查集来维护每行中每个格子的下一个未被染色的格子。初始化中下一个未染色的即为本身,而在当前该格子被染后,我们需要与下一个可能被染色的格子建立联系,即 li[i].Union(j,j+2),通过语句 j=li[i].find(j) 来到达下一个未被染色的点,从而实现操作。

void paint(query zo)
{
	int x=zo.x,xx=zo.xx,y=zo.y,yy=zo.yy,c=zo.c;
	for(int i=x;i<=xx;i++)
		for(int j=li[i].find(y+((i-x)&1));j<=yy;j=li[i].find(j))
		{
			co[i][j]=c;
			li[i].Union(j,j+2);
		}
	return;
}

注意:在语句 li[i].Union(j,j+2) 中,\(j+2\) 可能要大于 \(n-1\),如果在并查集的初始化中不对 \(n\)\(n+1\) 两个区域进行初始化,就会导致 \(j\) 可能成为 \(0\) 而陷入死循环。


AC Code 代码中根据个人习惯坐标都 \(+1\)

posted @ 2021-06-27 16:56  cyl06  阅读(42)  评论(0编辑  收藏  举报