1. 程式人生 > 實用技巧 >CF1455F. String and Operations Edu Round 99. 貪心

CF1455F. String and Operations Edu Round 99. 貪心

Edu Round 99 F. String and Operations


題目連結:https://codeforces.com/contest/1455/problem/F

\(t\)組資料,給你一個長度為\(n\)由前\(k\)個字元組成的字串。第\(i\)次操作你可以對原本在位置\(i\)的字元做\(4\)種操作中的一種或者不操作。

  • \(L:\) 若前面有字元就和前面的一個交換位置;
  • \(R:\) 若後面有字元就和後面的一個交換位置;
  • \(D:\) 迴圈左移一位,如:\(b\rightarrow a,a\rightarrow k_{-th}\;character\)
  • \(U:\) 迴圈右移一位,如:\(a\rightarrow b,k_{-th}\;character\rightarrow a\)

求經過\(n\)次操作後可以得到的字典序最小的字串。

對上述操作的解釋如下:

字串為testk=20經過操作URLD後字串變成:\(test\rightarrow aest\rightarrow aset\rightarrow saet\rightarrow saes\)

輸入:
6
4 2
bbab
7 5
cceddda
6 5
ecdaed
7 4
dcdbdaa
8 3
ccabbaca
5 7
eabba
輸出:
aaaa
baccacd
aabdac
aabacad
aaaaaaaa
abadb

思路

觀察知道,一個字元最多可以向前移動\(2\)個位置。比如\(abc\)

,第一次不操作,第二次操作\(R\),第三次操作\(L\)後就變成了\(cab\)

但是要將位置\(i+2\)的字元移動到位置\(i\)的話,它是沒辦法通過迴圈移動再變小的。

不過將位置\(i+1\)的字元移動到位置\(i\)的話, 他是可以做一次迴圈移動的,第一次操作\(R\),第二次操作\(D\;or\;U\)

那我們就貪心考慮每一位可能變換的最小值是多少。

假設經過合法改變後:現在在位置\(i\)的字元可以變成的最小字元為\(x\),現在在位置\(i+1\)的字元變到位置\(i\)後可以變成的最小字元為\(y\),現在在位置\(i+2\)的字元變到位置\(i\)後可以變成的最小字元為\(z\)

如果\(x\le y \&x\le z\)的話,位置\(i\)肯定還在位置\(i\)

如果\(y\lt x\& y\le z\)的話,位置\(i+1\)字元肯定是要移動到位置\(i\)的。

如果\(z\lt x\& z\lt y\)的話,位置\(i+2\)字元肯定是要移動到位置\(i\)的。

到這裡都是鋪墊,下面來講具體做法:

\(origin[i]=0\) 表示\(i\)位置的字元原本就在\(i\)
\(down[i]=0\) 表示\(i\)位置的字元能進行迴圈移動。

  • 如果\(i+1\)的字元能移動到\(i\),需要保證位置\(i+1\)字元原本就在\(i+1\)
  • 如果\(i+2\)的字元能移動到\(i\),需要保證位置\(i+1\)字元原本就在\(i+1\)
  • 然後再根據\(down[i]\)\(down[i+1]\)求出\(x,y,z\)的結果。
  • 然後再根據相應的\(x,y,z\)大小改變\(origin[i+1],origin[i+2],down[i+1],down[i+2]\),詳見程式碼註釋。
#include <bits/stdc++.h>
#define fi first
#define se second
#define o2(x) (x) * (x)
#define mk make_pair
#define eb emplace_back
#define SZ(x) ((int)(x).size())
#define all(x) (x).begin(), (x).end()
#define clr(a, b) memset((a), (b), sizeof((a)))
#define rep(i, s, t) for(register int i = (s), LIM=(t); i < LIM; ++i)
#define per(i, s, t) for(register int i = (s), LIM=(t); i >= LIM; --i)
#define GKD std::ios::sync_with_stdio(false);cin.tie(0)
#define my_unique(x) sort(all(x)), x.erase(unique(all(x)), x.end())
using namespace std;
typedef long long LL;
typedef long long int64;
typedef unsigned long long uint64;
typedef pair<int, int> pii;
// mt19937 rng(time(NULL));//std::clock()
// mt19937_64 rng64(chrono::steady_clock::now().time_since_epoch().count());
// shuffle(arr, arr + n, rng64);
inline int64 read() {
    int64 x = 0;int f = 0;char ch = getchar();
    while (ch < '0' || ch > '9') f |= (ch == '-'), ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch =
    getchar(); return x = f ? -x : x;
}
inline void write(int64 x, bool f = true) {
    if (x == 0) {putchar('0'); if(f)putchar('\n');else putchar(' ');return;}
    if (x < 0) {putchar('-');x = -x;}
    static char s[23];
    int l = 0;
    while (x != 0)s[l++] = x % 10 + 48, x /= 10;
    while (l)putchar(s[--l]);
    if(f)putchar('\n');else putchar(' ');
}
int lowbit(int x) { return x & (-x); }
template <class T>
T big(const T &a1, const T &a2) {return a1 > a2 ? a1 : a2;}
template <class T>
T sml(const T &a1, const T &a2) {return a1 < a2 ? a1 : a2;}
template <typename T, typename... R>
T big(const T &f, const R &... r) {return big(f, big(r...));}
template <typename T, typename... R>
T sml(const T &f, const R &... r) {return sml(f, sml(r...));}
void debug_out() { cout << '\n'; }
template <typename T, typename... R>
void debug_out(const T &f, const R &... r) {
    cout << f << " ";
    debug_out(r...);
}
#ifdef LH_LOCAL
#define debug(...) cout << "[" << #__VA_ARGS__ << "]: ", debug_out(__VA_ARGS__);
#else
#define debug(...) ;
#endif
/*================Header Template==============*/
const int mod = 998244353;// 998244353
int ksm(int a, int64 b, int kmod = mod) {int res = 1;for(;b > 0;b >>= 1, a = (int64)a * a % kmod) if(b &1) res = (int64)res * a % kmod;return res;}
const int INF = 0x3f3f3f3f;
const int MXN = 2e5 + 5;

int n, m;
char s[MXN];
int ori[MXN], dwn[MXN];
/*
origin[i] 表示i位置的字元是否原本就在i。
down[i] 表示i位置的字元能否迴圈移動。
如果i+1的字元能移動到i,需要保證i+1原本就在i+1。
如果i+2的字元能移動到i,需要保證i+1原本就在i+1。
如果經過合法改變後,從i來的字元是最小的就選i位置保持不變,否則如果從i+1來的字元是最小的就選i+1位置,否則如果從i+2來的字元是最小的就選i+2位置,否則就不變。
*/
void work() {
    n = read(), m = read();
    rep(i, 0, n) ori[i] = dwn[i] = 0;
    scanf("%s", s);
    rep(i, 0, n) {
        int a = s[i] - 'a', b = (i + 1 < n && ori[i + 1] == 0? s[i + 1] - 'a': INF), c = (i + 2 < n && ori[i + 1] == 0? s[i + 2] - 'a': INF);
        //位置i可以迴圈移動
        if(dwn[i] == 0) a = sml(a, (a + 1) % m, (a + m - 1) % m);
        //位置i+1可以迴圈移動
        if(b != INF && ori[i] == 0 && dwn[i + 1] == 0 && ori[i + 1] == 0) b = sml(b, (b + 1) % m, (b + m - 1) % m);
        if(a <= b && a <= c) {
            s[i] = a + 'a';
        }else if(b <= a && b <= c) {
            swap(s[i], s[i + 1]);
            if(s[i] != b + 'a') {//這時候i要必須先做操作R,所以改變後i+1沒法迴圈移動
                s[i] = b + 'a';
                dwn[i + 1] = 1;
            }else {
                //這時候i+1位置是不需要經過迴圈移動來變成更小的元素的,所以i位置是可能保留了一次操作,先不要急著操作,後面再慢慢考慮。
            }
            if(dwn[i]) dwn[i + 1] = 1;//但是如果原本i本來就沒法迴圈移動,那麼改變後i+1也沒法迴圈移動
            ori[i + 1] = 1;//i+1肯定不是原位置的啦
        }else if(c <= a && c <= b) {
            swap(s[i], s[i + 2]);
            swap(s[i + 1], s[i + 2]);
            //這時候其實i位是可能能保留了一次操作,先不要急著操作,後面再慢慢考慮。
            if(dwn[i]) dwn[i + 1] = 1;//但是如果原本i本來就沒法迴圈移動,那麼改變後i+1也沒法迴圈移動
            ori[i + 1] = 1;//i+1肯定不是原位置的啦
            ori[i + 2] = dwn[i + 2] = 1;//原本i+1做了操作R,所以改變後肯定沒法做迴圈移動操作,也不是原位置
        }
        // debug(i, s)
    }
    printf("%s\n", s);
}
int main() {
#ifdef LH_LOCAL
    freopen("D:/ACM/mtxt/in.txt", "r", stdin);
    // freopen("D:/ACM/mtxt/out.txt", "w", stdout);
#endif
    for(int cas = 1, tim = read(); cas <= tim; ++ cas) {
        // printf("Case #%d:\n", cas);
        work();
    }
#ifdef LH_LOCAL
    cout << "time cost:" << 1.0 * clock() / CLOCKS_PER_SEC << "s" << endl;
#endif
    return 0;
}