Google kickstart 2022 Round A題解

日沉雲起發表於2022-04-16

Speed Typing

題意概述

給出兩個字串IP,問能否通過刪除P中若干個字元得到I?如果能的話,需要刪除字元的個數是多少?

資料規模

\[1≤|I|,|P|≤10^5 \]

雙指標

設定兩個指標ij分別指向IP的第一個字元,滑動指標j,如果j指向的字元與i指向的字元相同,則讓i向後滑動一個字元,當i滑動到I字串末尾或j滑動到P字串末尾後即可結束迴圈。如果i滑動到I字串末尾,則說明可以通過刪除P中若干個字元得到I,那麼刪除的字元個數為\(|P|-|I|\);反之則不能。

複雜度

  • 時間複雜度為\(O(max(|I|,|P|))\)
  • 空間複雜度為\(O(1)\)

C++程式碼

#include <bits/stdc++.h>
using namespace std;
using gg = long long;
#define rep(i, a, b, c) for (gg i = (a); i <= (b); i += (c))
#define rrep(i, a, b, c) for (gg i = (a); i >= (b); i -= (c))
constexpr gg MAX = 1e6 + 5;
constexpr gg mod = 1e9 + 7;
constexpr gg INF = 4e18;
constexpr double eps = 1e-12;
gg ti, ni, mi, ki, di, pi, xi, yi;
gg up(gg n, gg m) { return n >= 0 ? (n + m - 1) / m : n / m; }
gg down(gg n, gg m) { return n >= 0 ? n / m : (n - m + 1) / m; }
//! ti; MAX; mod; 邊界
void solve() {
    string s1, s2;
    cin >> s1 >> s2;
    gg i = 0, j = 0;
    for (; i < s1.size() and j < s2.size(); ++i, ++j) {
        while (j < s2.size() and s1[i] != s2[j]) {
            ++j;
        }
        if (j == s2.size()) {
            break;
        }
    }
    i == s1.size() ? cout << s2.size() - s1.size() : cout << "IMPOSSIBLE";
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    ti = 1;
    cin >> ti;
    for (gg ii = 1; ii <= ti; ++ii) {
        cout << "Case #" << ii << ": ";
        solve();
        cout << "\n";
    }
    return 0;
}

Challenge Nine

題意概述

給定一個正整數\(N\),在給定的數字\(N\)的任意位置插入一個[0,9]之間的數字,得到一個不帶前導零的新的數字,需要保證這個新的數字是9的倍數。問能構造出的最小的數字是多少?

資料規模

\[1≤N≤10^{123456} \]

貪心

由於給出的數字非常大,需要用字串讀入。易知一個數字是9的倍數的充要條件是該數各位上的數字之和也是9的倍數。因此,先計算出讀取的字串各位上的數字之和sum,遍歷0~9這10個數字,假設當前遍歷到的數字是i,如果isum之和是9的倍數,說明插入i能夠保證新數字是9的倍數。接著從N的高位向低位遍歷,假設當前遍歷到的位上的數字是j,如果i<j,則將i插入到j之前可以得到最小的數字(想一想為什麼?)。

複雜度

  • 時間複雜度為\(O(n)\)
  • 空間複雜度為\(O(n)\)

其中\(n\)指的是數字N的位數。

C++程式碼

#include <bits/stdc++.h>
using namespace std;
using gg = long long;
#define rep(i, a, b, c) for (gg i = (a); i <= (b); i += (c))
#define rrep(i, a, b, c) for (gg i = (a); i >= (b); i -= (c))
constexpr gg MAX = 1e6 + 5;
constexpr gg mod = 1e9 + 7;
constexpr gg INF = 4e18;
constexpr double eps = 1e-12;
gg ti, ni, mi, ki, di, pi, xi, yi;
gg up(gg n, gg m) { return n >= 0 ? (n + m - 1) / m : n / m; }
gg down(gg n, gg m) { return n >= 0 ? n / m : (n - m + 1) / m; }
//! ti; MAX; mod; 邊界
void solve() {
    string n;
    cin >> n;
    gg m = n.size(), sum = 0;
    array<gg, 2> ans{m + 1, 10};
    for (char c : n) {
        sum += c - '0';
    }
    n.push_back(10 + '0');
    rep(i, 0, 9, 1) {
        if ((i + sum) % 9 == 0) {
            rep(j, 0, m, 1) {
                if (n[j] - '0' > i and (j > 0 or i != 0) and ans[0] > j) {
                    ans = {j, i};
                    break;
                }
            }
        }
    }
    n.pop_back();
    cout << n.substr(0, ans[0]) << ans[1] << n.substr(ans[0]);
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    ti = 1;
    cin >> ti;
    for (gg ii = 1; ii <= ti; ++ii) {
        cout << "Case #" << ii << ": ";
        solve();
        cout << "\n";
    }
    return 0;
}

Palindrome Free Strings

題意概述

給出一個長度為\(N\)的只包含01?構成的字串S,可以隨機用01替換掉字串S中所有的?,問能否找到一種替換方法,使得所得到的串沒有長度大於等於5的迴文子串。

資料規模

\[1<=N<=5\times 10^4 \]

動態規劃

首先需要注意到,如果字串S中包含長度為\(n(n>2)\)的迴文子串,那麼將該回文子串首尾兩個字元,那麼得到的長度為\(n-2\)的子串必然也是迴文的,因此,可以得到結論:如果S中不包含長度為5的迴文子串,那麼S中必然也不包含長度為\(5+2k(k>0)\)的迴文子串;如果S中不包含長度為6的迴文子串,那麼S中必然也不包含長度為\(6+2k(k>0)\)的迴文子串。因此,只要能夠找到一種替換方法使得S不包含長度為5和6的迴文子串,那麼S中必然也沒有長度大於等於5的迴文子串。

一種暴力的方法,是通過遞迴的方法將S中的字元?逐個替換為01,並驗證得到的字串中是否存在長度為5或6的迴文子串,時間複雜度為\(O(n\cdot 2^n)\),這種方法能夠通過小資料。下面主要介紹通過大資料的方法。

顯然,暴力方法的時間複雜度為指數級,可以通過動態規劃將時間複雜度降低到多項式級別。先進行分類討論:如果S長度小於5,那麼該字串無論怎麼替換?字元,都可以滿足要求,直接輸出POSSIBLE即可;如果S長度為5,那麼可以暴力列舉所有可能得到的替換後的字串,並驗證該字串是否為迴文串即可;下面主要討論S長度大於5的情況。

\(dp[i]\)表示以S的前i個字元能否找到一種替換方法保證沒有長度大於等於5的迴文子串。由於要驗證是否包含長度為6的迴文子串,那麼在每次新增新的字元S[i]時,S[i]能否構成長度為6的迴文子串與S[i]前面的5個字元S[i-5],S[i-4],S[i-3],S[i-2],S[i-1]有關,因此,可以為S[i]的前5個字元標記一個狀態,由於每個字元的取值只有01兩種,因此狀態總數為\(2^5\)。針對字元S[i],暴力列舉以S[i]結尾的長度為6的子串的可能替換結果字串j,如果j包含長度為5或6的迴文子串,則不符合題目要求;否則\(dp[i][j]=dp[i-1][j']\),其中\(j'=S[i-6]+j[:5]\)。具體實現可參考程式碼。

複雜度

  • 時間複雜度為\(O(2^K\cdot N)\)
  • 空間複雜度為\(O(2^K\cdot N)\)

其中\(K=5\)

C++程式碼

#include <bits/stdc++.h>
using namespace std;
using gg = long long;
#define rep(i, a, b, c) for (gg i = (a); i <= (b); i += (c))
#define rrep(i, a, b, c) for (gg i = (a); i >= (b); i -= (c))
constexpr gg MAX = 1e5 + 5;
constexpr gg MAX2 = 70;
constexpr gg mod = 1e9 + 7;
constexpr gg INF = 4e18;
constexpr double eps = 1e-12;
gg ti, ni, mi, ki, di, pi, xi, yi;
gg up(gg n, gg m) { return n >= 0 ? (n + m - 1) / m : n / m; }
gg down(gg n, gg m) { return n >= 0 ? n / m : (n - m + 1) / m; }
//! ti; MAX; mod; 邊界
bool dp[MAX][MAX2];
string s;
void dfs(gg p, gg r, string cur, vector<string>& v) {
    if (p > r) {
        v.push_back(cur);
        return;
    }
    if (s[p] != '1') {
        dfs(p + 1, r, cur + "0", v);
    }
    if (s[p] != '0') {
        dfs(p + 1, r, cur + "1", v);
    }
}
bool isPalic(const string& s) { return equal(s.begin(), s.end(), s.rbegin()); }
void solve() {
    cin >> ni >> s;
    if (ni <= 4) {
        cout << "POSSIBLE";
    } else if (ni == 5) {
        vector<string> v;
        dfs(0, 4, "", v);
        bool ans = all_of(v.begin(), v.end(), isPalic);
        cout << (ans ? "IMPOSSIBLE" : "POSSIBLE");
    } else {
        bool ans = false;
        rep(i, 5, ni - 1, 1) {
            vector<string> v;
            dfs(i - 5, i, "", v);
            for (string& k : v) {
                gg cur = stoll(k, 0, 2);
                dp[i][cur] = not(isPalic(k) or isPalic(k.substr(0, 5)) or isPalic(k.substr(1, 5)));
                if (i >= 6) {
                    bool res = false;
                    if (s[i - 6] != '1') {
                        res = res or dp[i - 1][stoll("0" + k.substr(0, 5), 0, 2)];
                    }
                    if (s[i - 6] != '0') {
                        res = res or dp[i - 1][stoll("1" + k.substr(0, 5), 0, 2)];
                    }
                    dp[i][cur] = dp[i][cur] and res;
                }
                if (i == ni - 1) {
                    ans = ans or dp[i][cur];
                }
            }
        }
        cout << (ans ? "POSSIBLE" : "IMPOSSIBLE");
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    ti = 1;
    cin >> ti;
    for (gg ii = 1; ii <= ti; ++ii) {
        cout << "Case #" << ii << ": ";
        solve();
        cout << "\n";
    }
    return 0;
}

Interesting Integers

題意概述

如果一個整數的所有數字的乘積能被所有數字的和整除,就稱這個整數為有趣的。給出兩個整數\(A\)\(B\),找出\([A,B]\)之間有趣的整數個數。

資料規模

\[1≤A≤B≤10^5(小資料) \]

演算法設計

目前只會解小資料,大資料的解法可參考Google Kick Start 2022 Round A 題解。通過列舉\([A,B]\)之間的數字,暴力判斷組成該整數的所有數字的乘積能能否被所有數字的和整除即可。

複雜度

  • 時間複雜度為\(O(n)\)
  • 空間複雜度為\(O(n)\)

其中\(n=B-A+1\)

C++程式碼

#include <bits/stdc++.h>
using namespace std;
using gg = long long;
#define rep(i, a, b, c) for (gg i = (a); i <= (b); i += (c))
#define rrep(i, a, b, c) for (gg i = (a); i >= (b); i -= (c))
constexpr gg MAX = 1e5 + 5;
constexpr gg MAX2 = 70;
constexpr gg mod = 1e9 + 7;
constexpr gg INF = 4e18;
constexpr double eps = 1e-12;
gg ti, ni, mi, ki, di, pi, xi, yi;
gg up(gg n, gg m) { return n >= 0 ? (n + m - 1) / m : n / m; }
gg down(gg n, gg m) { return n >= 0 ? n / m : (n - m + 1) / m; }
//! ti; MAX; mod; 邊界
bool func(gg n) {
    gg s = 0, p = 1;
    while (n > 0) {
        s += n % 10;
        p *= n % 10;
        n /= 10;
    }
    return p % s == 0;
}
void solve() {
    cin >> xi >> yi;
    gg ans = 0;
    rep(i, xi, yi, 1) {
        if (func(i)) {
            ++ans;
        }
    }
    cout << ans;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    ti = 1;
    cin >> ti;
    for (gg ii = 1; ii <= ti; ++ii) {
        cout << "Case #" << ii << ": ";
        solve();
        cout << "\n";
    }
    return 0;
}

相關文章