1. 程式人生 > 其它 >CSP-S 2021 T3 Palin 題解

CSP-S 2021 T3 Palin 題解

CSP-S 2021 T3 迴文 palin 題解

題目大意

給定一個 \(2*n\) 個數字的序列,每個數字都在 \(\{1,2,3,...,n\}\) 內,並且出現且僅出現兩次。

求一個操作序列,使得其滿足按序取出的數字序列為迴文序列。操作分為兩種:

  1. L:將原序列左端點處的數取出加入新序列尾部,並將原序列左端點處的數刪除。
  2. R:將原序列右端點處的數取出加入新序列尾部,並將原序列右端點處的數刪除。

多測,資料組數 \(T\leq 100\)\(1\leq n,\sum n \leq 2\times 10^5\)

分析

首先必然考慮第一次操作。無論如何操作,我們都能找到與第一次取出的數 \(x\)\(x\) 必然在左端點和右端點中取一個) 相同的數。我們不妨假設我們在下標 \(i\)

出找到了另外一個 \(x\)

手玩下樣例:

5
4 1 2 4 5 3 1 2 3 5

這裡我們第一次取出的是左端點,那麼我們找到與左端點相同的那個數 \(4\),它的下標正好也在第 \(4\) 位。

並且,正因為我們第一次取出的是 \(4\),因此我們顯然必須將內部第 \(i\) 位的那個 \(4\) 放在最後一位取出。

與此同時,我們得到了把這兩個 \(4\) 給搞定的操作:

  • 由於從左端點取出了一個 \(4\),那麼第一步操作為 L
  • 由於最終的這個 \(4\) 無論如何都是在只有一個元素的序列中取出的,那麼答案無論怎麼變都是 L

搞定了這兩個 \(4\) 我們接著來看剩下的序列。

(4) 1 2 (4) 5 3 1 2 3 5

由於每次只能取剩下的左端點和右端點,所以這裡只能取 \(1\)\(5\) 中間的一個。

我們發現這裡另外 \(1\) 根本不在中間那個 \(4\) 的左右,而如果這一步取 \(1\),那麼倒數第二步必然得取 \(1\),那麼最後幾步將無法操作。

考慮取一下 \(5\),我們發現 \(5\)\(4\) 旁邊,那麼這一步取 \(5\) 就是非常合法的,(至少對於現在已經處理的前兩步和倒數兩步而言)。

這裡我們來思考一下這兩個 \(5\) 是由什麼樣的操作得到的:

  • 由於從右端點取出了一個 \(5\),那麼第二步操作為 R
  • 由於從 \(4\)
    的右邊取出了另外一個 \(5\),考慮最終剩下來應該長成什麼樣,大概是:4 5 這樣,那麼倒數第二步也應該是 R

這時候應該找到一些規律了。但我們接著把這個樣例講完,剩下:

(4) 1 2 (4 5) 3 1 2 3 (5)

發現右端有一個 \(3\)\(5\) 右邊有一個 \(3\),那麼把這兩個 \(3\) 給取出來。這兩步應該都是 R

(4) 1 2 (4 5 3) 1 2 (3 5)

左端點和 \(3\) 的右邊都是 \(1\),因此都取出來,這兩步應該一個是 L,一個是 R

(4 1) 2 (4 5 3 1) 2 (3 5)

這個時候取兩個 L 就行。


觀察上面的手模過程,我們發現:

  1. 每一步可以確定兩個位置及其操作。
  2. 從第二步開始,每個數必然只能從內部已經取過的數的兩邊來擴充套件。

形象地來說,內部已經取過的數字可以構成一根“線段”。

由於第一步無法確定從左開始取還是從右開始取,分類討論即可。

這兩個性質無論對於第一步為 L 還是對於第一步為 R 都是成立的。通過這兩個性質我們可以 \(O(1)\) 的計算出一個序列下一步的操作。

另外,如果一個序列在經過若干次擴充套件後無法繼續擴充套件,那麼無解,因為優先擴充套件左邊和右邊對於其餘部分無影響,如果能夠擴充套件就一定會在某一時刻進行擴充套件。


具體實現呢?

首先,對於第一步分類討論,找到左端點或右端點所對應的內部點(最後一個被取出來的位置),打上標記。我們不妨稱其為 \(\operatorname{inner}\)

接下來我們同時維護四根指標:

  • \(\operatorname{outl}\)\(\operatorname{outr}\),負責維護在執行完前繼操作後兩端端點在原序列中的位置。
  • \(\operatorname{inl}\)\(\operatorname{inr}\),負責維護在執行完前繼操作後內部已操作線段的兩端端點在原序列中的位置。

在執行完每一步操作後對這四根指標進行更新。我們優先進行 \(\operatorname{outl}\) 對於 \(\operatorname{inl}\)\(\operatorname{inr}\) 的匹配,原因是輸出字典序最小的。

注意如果在某一時刻 \(\operatorname{inl}\leq\operatorname{outl}\),那麼不能繼續擴充套件,否則會將已經擴充套件過的部分再反向擴充套件一遍。

程式碼

#include <bits/stdc++.h>
#define HohleFeuerwerke using namespace std
#define int long long
HohleFeuerwerke;
inline int read() {
    int s = 0, f = 1;
    char c = getchar();

    for (; !isdigit(c); c = getchar())
        if (c == '-')
            f = -1;

    for (; isdigit(c); c = getchar())
        s = s * 10 + c - '0';

    return s * f;
}
inline void write(int x) {
    if (x < 0)
        putchar('-'), x = -x;

    if (x >= 10)
        write(x / 10);

    putchar('0' + x % 10);
}
const int MAXN = 1e6 + 5;
int T, n;
int a[MAXN], ans[MAXN];
inline void solve() {
    char Ans[MAXN];
    memset(Ans, 0, sizeof(Ans));
    int cnt = 0;
    memset(ans, 0, sizeof(ans));
    int inner = 0;
    int outl = 1, outr = 2 * n;

    for (int i = 2; i <= 2 * n; i++) {
        if (a[i] == a[1]) {
            inner = i;
            break;
        }
    }

    int inl = inner, inr = inner;

    for (int i = 1; i <= n; i++) {
        if (a[inl] == a[outl] && inl > outl) {
            if (inl == inr)
                inl--, inr++, outl++;
            else
                inl--, outl++;

            ans[++cnt] = a[inl + 1];
            ans[2 * n - cnt + 1] = a[inl + 1];
            Ans[cnt] = 'L', Ans[2 * n - cnt + 1] = 'L';
        } else if (a[inr] == a[outl] && inr != outl) {
            if (inl == inr)
                inl--, inr++, outl++;
            else
                inr++, outl++;

            ans[++cnt] = a[inr - 1];
            ans[2 * n - cnt + 1] = a[inr - 1];
            Ans[cnt] = 'L', Ans[2 * n - cnt + 1] = 'R';
        } else if (a[inl] == a[outr] && inl != outr) {
            inl--;
            outr--;
            ans[++cnt] = a[inl + 1];
            ans[2 * n - cnt + 1] = a[inl + 1];
            Ans[cnt] = 'R', Ans[2 * n - cnt + 1] = 'L';
        } else if (a[inr] == a[outr] && inr < outr) {
            inr++;
            outr--;
            ans[++cnt] = a[inr - 1];
            ans[2 * n - cnt + 1] = a[inr - 1];
            Ans[cnt] = 'R', Ans[2 * n - cnt + 1] = 'R';
        } else
            goto End;
    }

    printf("%s\n", Ans + 1);
    return;
End:
    ;
    cnt = 0;
    memset(ans, 0, sizeof(ans));
    memset(Ans, 0, sizeof(Ans));
    inner = 0;
    outl = 1, outr = 2 * n;

    for (int i = 2; i <= 2 * n; i++) {
        if (a[i] == a[2 * n]) {
            inner = i;
            break;
        }
    }

    inl = inner, inr = inner;

    for (int i = 1; i <= n; i++) {
        if (a[inl] == a[outl] && inl > outl) {
            inl--, outl++;
            ans[++cnt] = a[inl + 1];
            ans[2 * n - cnt + 1] = a[inl + 1];
            Ans[cnt] = 'L', Ans[2 * n - cnt + 1] = 'L';
        } else if (a[inr] == a[outl] && inr != outl) {
            inr++, outl++;
            ans[++cnt] = a[inr - 1];
            ans[2 * n - cnt + 1] = a[inr - 1];
            Ans[cnt] = 'L', Ans[2 * n - cnt + 1] = 'R';
        } else if (a[inl] == a[outr] && inl != outr) {
            if (inl == inr)
                inl--, inr++, outr--;
            else
                inl--, outr--;

            ans[++cnt] = a[inl + 1];
            ans[2 * n - cnt + 1] = a[inl + 1];
            Ans[cnt] = 'R', Ans[2 * n - cnt + 1] = 'L';
        } else if (a[inr] == a[outr] && inr < outr) {
            if (inl == inr)
                inl--, inr++, outr--;
            else
                inr++, outr--;

            ans[++cnt] = a[inr - 1];
            ans[2 * n - cnt + 1] = a[inr - 1];
            Ans[cnt] = 'R', Ans[2 * n - cnt + 1] = 'R';
        } else {
            puts("-1");
            return;
        }
    }

    printf("%s\n", Ans + 1);
}
signed main() {
    freopen("palin.in", "r", stdin);
    freopen("palin.out", "w", stdout);
    T = read();

    while (T--) {
        n = read();

        for (int i = 1; i <= 2 * n; i++)
            a[i] = read();

        solve();
    }
}