ZJNU 26?? - SLIKA

题面

ZJNU 2663 - SLIKA (并查集)_逆序

有一个\(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;
}