题解:P11113 [ROI 2024] 2026 (Day 1)

· · 题解

P11113 [ROI 2024] 2026 (Day 1) 题解

博客园地址:P11113 [ROI 2024] 2026 (Day 1) 题解 - Add_Catalyst - 博客园 (cnblogs.com)。

知识点

贪心,模拟。

分析

首先知道暴力可以直接 O(\sum nmQ) 的解决。

简化操作序列

发现对于操作序列中,连在一起的 LL 等相同的字符可以缩掉,然后再进一步发现,如果同是水平方向的操作(LR)挨在一起,那么只需要留最后一个即可,例如 LRLRLRLLLRLRL 保留 L。那么对于竖直方向的 UD 也是同理。

现在操作序列相邻的操作方向不可能平行,例如 LULURD,但是我们发现还可以简化,比如中间 LUL 的部分,发现可以证明右边那个 L 是无效的,其余也是类似。

所以我们得到了两条简化操作序列的规则:

  1. 连续的方向平行的操作只保留最后一个
  2. 如果这次操作与上一次方向平行的操作相同,那么可以直接忽略。

这个过程可以用栈来实现。

循环节性质

发现简化完的操作序列一定是四个方向操作按某个顺序各做一次作为循环节,然后多次重复形成的一个循环,最后可能还有多 1\sim 3 个操作。证明也较简单,只需根据上面两条规则即可。

我们定义矩形状态“点全部在矩形一角”为:做往这个角两种方向的操作,内部的字母不会移动。例如,当“点全部在矩形左上角”,那么做 LU 操作是无效的。

那么我们把这个循环节拆出来,就发现:如果点全部在矩形一角,那么做一遍循环节上的操作(后称为“循环节变换”),原本有字母的位置还是有字母,没有字母的位置还是没有字母,但是中间字母的位置是变换的。

我们考虑把矩阵的每个有字母的点放入一张图,那么这个点在经过循环节变换后会到达另一个点,我们连一条单向边。发现每个点有一条出边和一条入边,所以整张图就形成了一堆简单环。

那么我们只要做一点边界处理,然后在环上对操作数取个模就可以找到答案。

边界处理

由于上述性质是基于“点全部在矩形一角”这个状态,我们可以取出少量的操作先暴力做,剩下的操作再放入循环节,这样性质是不变的。取出的操作次数至少为两次,可以在这里把循环节之外多的 1\sim 3 个操作一并处理掉。

那么就可以分情况处理:(设 Q 为简化后的操作序列长度)

代码

复杂度 O(\sum nm)。代码写得稍有凌乱,但是总体还算简洁。

constexpr int N(1e6+10);

bool vis[N];
char s[N];
int Cas,n,m,t,Q;
int fa[N];
vector<int> a[N],b[N],idx[N];

int X(int u) { return (u-1)/m+1; }

int Y(int u) { return u%m?u%m:m; }

int ID(int x,int y) { return (x-1)*m+y; }

void Up(vector<int> *a) {
    FOR(j,1,m) {
        int tmp(0);
        FOR(i,1,n)if(~a[i][j]) {
            int num(a[i][j]);
            a[i][j]=-1,a[++tmp][j]=num;
        }
    }
}

void Down(vector<int> *a) {
    FOR(j,1,m) {
        int tmp(n+1);
        DOR(i,n,1)if(~a[i][j]) {
            int num(a[i][j]);
            a[i][j]=-1,a[--tmp][j]=num;
        }
    }
}

void Left(vector<int> *a) {
    FOR(i,1,n) {
        int tmp(0);
        FOR(j,1,m)if(~a[i][j]) {
            int num(a[i][j]);
            a[i][j]=-1,a[i][++tmp]=num;
        }
    }
}

void Right(vector<int> *a) {
    FOR(i,1,n) {
        int tmp(m+1);
        DOR(j,m,1)if(~a[i][j]) {
            int num(a[i][j]);
            a[i][j]=-1,a[i][--tmp]=num;
        }
    }
}

int Output(vector<int> *a) {
    FOR(i,1,n) {
        FOR(j,1,m)putchar(~a[i][j]?'a'+a[i][j]:'.');
        putchar('\n');
    }
    return 0;
}

int Cmain() {
    /*DE("Input");*/
    I(n,m),t=n*m;
    FOR(i,1,n) {
        scanf("%s",s+1),a[i]=vector<int>(m+1,-1);
        FOR(j,1,m)if(s[j]!='.')a[i][j]=s[j]-'a';
    }
    /*DE("Query");*/
    scanf("%s",s+1),Q=0;
    int tmp(strlen(s+1));
    auto typ=[&](char a) { return a=='L'||a=='R'; };
    FOR(i,1,tmp) {
        char c(s[i]);
        while(Q&&typ(s[Q])==typ(c))--Q;
        if(Q<2||s[Q-1]!=c)s[++Q]=c;
    }
    /*DE("Check");*/
    if(Q<=8) {
        FOR(i,1,Q)s[i]=='L'?Left(a):(s[i]=='R'?Right(a):(s[i]=='U'?Up(a):Down(a)));
        return Output(a);
    }
    /*DE("Build");*/
    tmp=4+Q%4;
    FOR(i,1,tmp)s[i]=='L'?Left(a):(s[i]=='R'?Right(a):(s[i]=='U'?Up(a):Down(a)));
    Q-=tmp;
    FOR(i,1,Q)s[i]=s[i+tmp];
    FOR(i,1,n) {
        idx[i]=vector<int>(m+1,-1);
        FOR(j,1,m)if(~a[i][j])idx[i][j]=ID(i,j);
    }
    FOR(i,1,4)s[i]=='L'?Left(idx):(s[i]=='R'?Right(idx):(s[i]=='U'?Up(idx):Down(idx)));
    Q/=4;
    FOR(i,1,n)FOR(j,1,m)if(~idx[i][j])fa[idx[i][j]]=ID(i,j);
    /*DE("Trans");*/
    FOR(i,1,n)b[i]=vector<int>(m+1,-1);
    FOR(i,1,n)FOR(j,1,m)if(~a[i][j]&&!vis[ID(i,j)]) {
        const int u(ID(i,j));
        vector<int> tmp {u};
        for(int v(fa[u]); v&&v!=u; v=fa[v])tmp.push_back(v),vis[v]=true;
        int y(Q%tmp.size());
        for(int x:tmp)b[X(tmp[y])][Y(tmp[y])]=a[X(x)][Y(x)],y=(y+1)%tmp.size();
    }
    /*DE("Output");*/
    return Output(b);
}

signed main() {
#ifdef Plus_Cat
    freopen(Plus_Cat ".in","r",stdin),freopen(Plus_Cat ".out","w",stdout);
#endif
    for(I(Cas); Cas; --Cas) {
        Cmain();
        /*DE("Clear");*/
        FOR(i,1,t)vis[i]=false;
        FOR(i,1,n)a[i].clear(),b[i].clear(),idx[i].clear();
    }
    return 0;
}