题面
有一个\(n\times n\)的网格图,\(k\)种颜色,\(m\)次操作,棋盘初始时颜色均为\(1\)。
操作分三种,第一种操作\(PAINT\)会选择一个矩形区域与一种颜色,要求按棋盘方式对选定的区域涂色,如上图所示;后涂的颜色会覆盖在先前涂的颜色上。
第二种操作\(SAVE\)会保存一个副本,递增编号。
第三种操作\(LOAD\)则会加载先前保存的副本。
问按顺序执行完所有操作后,网格最终的状态。
思路
忘了哪里有做到过一次,也是逆序还原操作来优化
至于利用并查集来跳过已使用的部分的话,印象里2020ICPC银川站的某道银冲金题(加点手速和少罚时)也是这个做法,算是比较常见的
按顺序进行,时间复杂度\(O(m\cdot n^2)\),空间复杂度\(O(m\cdot n^2)\),均不可行。
首先考虑\(SAVE\)和\(LOAD\)操作,执行\(LOAD\)操作后,会跳转至先前的某一存档,也就说明从那个存档开始到当前的\(LOAD\)操作之间的所有\(PAINT\)在此时均被忽略掉了。
发现按顺序进行\(PAINT\)操作时,后面进行的操作的涂色会将先前的涂色给覆盖掉;也就是说如果逆序看所有的操作,产生位置冲突时后面的操作将会跳过该位置的涂色。
因此,如果我们能够快速将该跳过的位置直接跳过,最终涂色操作最多也就只会做\(n^2\)级别次,而不是按顺序执行所有操作时最坏情况下需要进行\(m\cdot n^2\)级别次。
那么考虑快速跳过已经被涂色的位置,发现可以将每一行都单独看作一个并查集,如果我们只枚举待涂色区域的某一维(行),然后借助并查集跳过已经被涂色的位置(列),那么最终的时间复杂度实际上只有操作数与枚举数的乘积,即\(O(m\cdot n)\)。
考虑实现,假设当前我们将位置\((i,j)\)进行涂色,保证行独立处理(\(i\)不变),因此向右的下一个涂色的位置应是\((i,j+2)\)。定义\(to[i][j]\)表示在位置\((i,j)\)时,下一个待处理的\(y\)轴坐标,利用并查集维护。那么每次涂色之后执行的应当就是将\(to[i][j]\)合并到\(to[i][j+2]\)(注意向右合并)。合并后则直接让\(y=find(x,to[x][y])\)跳至下一个\(y\)坐标即可(\(find(x,y)\)函数即对于\(x\)行的查询),借助路径压缩优化过程。
最后考虑\(SAVE\)和\(LOAD\),在倒序看所有操作时,每一次\(LOAD\)实际上就是跳过了与其对应的\(SAVE\)操作到它之间的所有\(PAINT\)操作(在那一次\(SAVE\)之后的所有\(PAINT\)都因为这一次\(LOAD\)无效了),所以该部分简单处理以下,其后直接跳转操作数即可。
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define perr(i,a,b) for(int i=(a);i>(b);i--)
#define all(a) (a).begin(),(a).end()
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
using namespace std;
typedef long long ll;
int n,k,q;
struct node
{
int op,clr,x1,y1,x2,y2;
}ar[100050];
int savepos[100050],loadto[100050],savecnt=0;
// PAINT SAVE LOAD
int to[1050][1050];
int fnd(int x,int a){
return a==to[x][a]?a:(to[x][a]=fnd(x,to[x][a]));
}
void merge(int x,int a,int b){
to[x][a]=fnd(x,to[x][b]);
}
int ans[1050][1050];
void solve()
{
cin>>n>>k>>q;
repp(i,0,n)
repp(j,0,n)
ans[i][j]=1;
rep(i,1,q)
{
string s;
cin>>s;
if(s[0]=='P')
{
ar[i].op=1;
cin>>ar[i].clr;
cin>>ar[i].x1;
cin>>ar[i].y1;
cin>>ar[i].x2;
cin>>ar[i].y2;
}
else if(s[0]=='S')
{
ar[i].op=2;
}
else
{
ar[i].op=3;
cin>>ar[i].clr;
}
}
rep(i,1,q)
{
if(ar[i].op==2)
savepos[++savecnt]=i;
}
rep(i,1,q)
{
if(ar[i].op==3)
loadto[i]=savepos[ar[i].clr];
}
repp(x,0,n)
rep(y,0,n+1) //y会与y+2合并,注意边界情况
{
to[x][y]=y;
}
per(i,q,1)
{
if(ar[i].op==1)
{
rep(x,ar[i].x1,ar[i].x2)
{
int y=ar[i].y1+(x-ar[i].x1)%2; //起始y
for(y=fnd(x,to[x][y]);y<=ar[i].y2;y=fnd(x,to[x][y]))
{
ans[x][y]=ar[i].clr;
merge(x,y,y+2); //涂色后将y与y+2合并
}
}
}
else if(ar[i].op==2)
continue;
else
i=loadto[i];
}
repp(i,0,n)
repp(j,0,n)
cout<<ans[i][j]<<(j==n-1?'\n':' ');
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
solve();
return 0;
}