AtCoder Beginner Contest 175 (AB水,C數學,D思惟+前綴和處理+進價思考,E方陣+條件DP,F新迴文字符串處理 GJ)

補題連接:Hereios

A - Rainy Season

若是不是 RSR 型的話直接計算 R 的數量便可c++

B - Making Triangle

給定 \(N\) 根長度分別爲 \(L_i\) 的棍子,問能組成多少個三邊長度各不相同的三角形?若是兩個三角形至少用了一根不一樣編號的棍子,則稱它們是不一樣的三角形。算法

因爲數據範文較小 (\(N \le 100\)),因此咱們能夠排序之後枚舉三元組便可。優化

另外 CP wiki 提到這裏進一步優化的話,能夠在固定最長邊的基礎上,用雙指針肯定另外兩條邊的長度範圍,這樣時間複雜度就降到的了 \(\mathcal{O}(N^2)\)spa

C - Walking Takahashi

題意:有一個數 \(X\) ,對它進行 \(K\)\(+D\)\(−D\) 的操做,求操做後的 \(\min|X'|\)指針

思路:code

首先XX的正負不影響結果,因此咱們能夠只考慮 \(|X|\)排序

若是 \(|X|>D\),那麼咱們首先應該向原點移動,直到 \(|X'|<D\)。這時還剩下 \(K′\) 次操做,咱們應當在原點的左右兩側來回移動。根據 \(K′\) 的奇偶判斷一下最後在哪個位置便可。ci

using ll = long long;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    ll X, K, D, R;
    cin >> X >> K >> D;
    if (X < 0) X = -X;
    R = X / D;
    if (K < R) {
        cout << (X - K * D);
        return 0;
    }
    K -= R, X -= R * D;
    cout << (K & 1 ? D - X : X);
    return 0;
}

D - Moving Piece

\(N(N\leq5000)\)個方格,從第 \(i\) 個方格會跳到第 \(P_i\) 個方格。PP是 \(1,\cdots,N\)的一個排列。字符串

每一個方格上寫了一個數字 \(C_i\) 。每次跳躍時,會獲得等同於 \(C_{P_i}\) 的分數。你能夠從任意方格開始,跳躍至少一次,至多 \(K\) 次,求可以取得的最高分數。

思路:枚舉起點。因爲 \(P\) 是排列,因此咱們從任意位置 \(i\) 開始,通過若干次跳躍後必定會回到 \(i\) 。咱們能夠計算出一個週期內的前綴和。而後,根據週期長度 \(C\)\(K\) 之間的關係,分狀況討論。

  • \(K\leq C\),此時咱們應該選擇前 \(K\) 個前綴和中的最大值。

  • \(K>C\),令\(K=nc+r\),則咱們能夠選擇

    • 不循環,選擇全部前綴和中的最大值。
    • 循環 \(n\) 次,再加上前 \(r\) 個前綴和中的最大值。
    • 循環 \(n−1\) 次,再加上全部前綴和中的最大值。
  • \(\mathcal{O}(N^2)\)

// Murabito-B 21/04/08
#include <bits/stdc++.h>
using ll = long long;
using namespace std;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    int n, k;
    cin >> n >> k;
    vector<int> p(n), c(n);
    for (int i = 0; i < n; ++i) cin >> p[i];
    for (int i = 0; i < n; ++i) cin >> c[i];
    ll ans = LLONG_MIN; // long long的最小值
    for (int i = 0; i < n; ++i) {
        vector<bool> book(n);
        int idx        = i;
        vector<ll> sum = {0}, hi = {LLONG_MIN};
        while (!book[p[idx] - 1]) {
            idx       = p[idx] - 1;
            book[idx] = true;
            sum.emplace_back(sum.back() + c[idx]);
            hi.emplace_back(max(hi.back(), sum.back()));
        }
        int m = sum.size() - 1;
        int f = k / m, res = k % m;
        ll result = 0;
        if (f > 0) result = max(hi[m], max(sum[m] * f + (res == 0 ? 0 : hi[res]), sum[m] * (f - 1) + hi[m]));
        else
            result = hi[res];
        ans = max(ans, result);
    }
    cout << ans << "\n";
    return 0;
}

另外若是 \(N \le 10^5\) 呢?應該如何改進算法?

這裏想了好久,只想到了 RMQ解決但代碼部分沒寫出來,只能轉載一下 CP wiki 的了

提示一:

在上面的算法中,對於一個循環,設其長度爲 \(L\) ,咱們實際上重複計算了 \(L\) 次(針對每個起點)。有沒有可能減小這樣的重複計算呢?

提示二

在每個循環內,問題實際上能夠轉化爲,給定一個由 \(L\) 個數圍成的圈,從中取出長度不超過\(K\)的一段連續串,求能取得的最大和。

提示三

前綴和+RMQ。

// Murabito-B 21/04/08
#include <bits/stdc++.h>
using ll = long long;
#define MAXN 5005
#define K 15
using namespace std;
const ll LO = -1e16;
int n, k;

ll st[MAXN * 2][K];

ll query(int l, int r) {
    int len = r - l + 1;
    int j   = log2(len);
    return min(st[l][j], st[r - (1 << j) + 1][j]);
}

ll solve(vector<int> &v) {
    int len      = v.size();
    vector<ll> s = {0};
    for (int i = 0; i < 2 * len; ++i)
        s.emplace_back(s.back() + v[i % len]);
    int slen = s.size();
    for (int i = 0; i < slen; ++i)
        st[i][0] = s[i];
    for (int j = 1; j <= log2(slen); ++j)
        for (int i = 0; i < slen; ++i) {
            st[i][j]  = st[i][j - 1];
            int right = i + (1 << (j - 1));
            if (right < slen)
                st[i][j] = min(st[i][j], st[right][j - 1]);
        }
    ll sum = s[len], hi_r = LO, hi_all = LO;
    int r = k % len;
    for (int i = 1; i < slen; ++i) {
        if (r)
            hi_r = max(hi_r, s[i] - query(max(0, i - r), i - 1));
        hi_all = max(hi_all, s[i] - query(max(0, i - len), i - 1));
    }
    if (k < len)
        return hi_r;
    return max(hi_all, max(sum * (k / len - 1) + hi_all, sum * (k / len) + hi_r));
}

int main() {
    cin >> n >> k;
    vector<int> p(n), c(n);
    for (int i = 0; i < n; ++i)
        cin >> p[i];
    for (int i = 0; i < n; ++i)
        cin >> c[i];
    ll ans = LO;
    vector<bool> vis(n);
    for (int i = 0; i < n; ++i) {
        if (vis[i])
            continue;
        vector<int> v;
        int idx = i;
        while (!vis[p[idx] - 1]) {
            idx      = p[idx] - 1;
            vis[idx] = true;
            v.emplace_back(c[idx]);
        }
        ans = max(ans, solve(v));
    }
    cout << ans;
}

E - Picking Goods

\(R\)\(C\) 列的方陣,其中有 \(K\) 個格子裏有東西,第ii個東西的價值爲 \(v_i\)。從左上角走到右下角,只能向下或向右走,限定每行最多拿 $ 3$ 個東西,求能取得的最大價值。

簡單的方陣 DP 再加一維記錄當前行取了幾個東西便可。由於\(3\) 是常數,因此總時間複雜度爲:\(\mathcal{O}(RC)\)

// Murabito-B 21/04/08
#include <bits/stdc++.h>
using ll = long long;
using namespace std;
ll dp[3010][3010][4] = {0};
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    int R, C, K;
    cin >> R >> C >> K;
    vector<vector<int>> a(R + 1, vector<int>(C + 1));
    for (int i = 0; i < K; ++i) {
        int r, c, v;
        cin >> r >> c >> v;
        a[r][c] = v;
    }
    for (int i = 1; i <= R; ++i)
        for (int j = 1; j <= C; ++j) {
            for (int k = 0; k <= 3; ++k)
                dp[i][j][0] = max(dp[i][j][0], dp[i - 1][j][k]);
            for (int k = 0; k <= 3; ++k)
                dp[i][j][k] = max(dp[i][j][k], dp[i][j - 1][k]);
            if (a[i][j])
                for (int k = 3; k > 0; --k)
                    dp[i][j][k] = max(dp[i][j][k], dp[i][j][k - 1] + a[i][j]);
        }
    ll ans = 0;
    for (int i = 0; i <= 3; ++i) ans = max(ans, dp[R][C][i]);
    cout << ans << "\n";
    return 0;
}

F - Making Palindrome

F 題是懵逼ing

\(N\)\(N\leq50\))個長度不超過 \(L\)\(L\leq20\))的字符串,每一個字符串能夠使用無限次,第ii個字符串使用一次的代價爲 \(C_i\)。問最少花費多少代價,可以用這些字符串組成一個迴文串?或者說明無解。

大佬題解:

直接搜索,狀態彷佛是無窮無盡的。如何減小狀態空間,讓搜索變爲可能?

咱們考慮從左右兩邊分別構建字符串。最開始,左邊和右邊都是空的。咱們但願最後能將左邊部分和右邊部分進行匹配。這裏,匹配的意思是,對於串 \(A\)\(B\),兩串中較短的那串是較長那串的子串。在匹配以後,若是剩下的部分是一個迴文串(或爲空),則咱們就成功構建了一個迴文串。

咱們每次能夠把某個字符串加入到左邊或右邊,這樣就獲得一箇中間狀態。在轉移過程當中,咱們應當保證始終只有至多一邊有未匹配部分,而其他部分都應該獲得匹配。也就是說,若是當前左邊有未被匹配的部分,咱們就把新字符串添加到右邊;反之亦然。

從而,咱們只須要保存當前未被匹配的部分。而由於咱們老是在相反的一邊添加,這裏的未被匹配部分一定爲原來某個字符串的前綴或後綴。這樣,咱們就把總狀態數限制到了\(O(NL)\)

此時,原題就變成了一個最短路徑問題。由於數據範圍很小,能夠用各類最短路徑算法來求解。

// Murabito-B 21/04/08
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define INF 10000000000000000LL
bool is_palindrome(string &s) {
    int n = s.size();
    for (int i = 0; i < n / 2; ++i)
        if (s[i] != s[n - i - 1])
            return false;
    return true;
}

int n;
unordered_map<string, ll> memo[2];
unordered_set<string> vis[2];
vector<string> S[2];
vector<ll> C;
ll dfs(string s, int p) {
    if (memo[p].count(s))
        return memo[p][s];
    if (is_palindrome(s))
        return 0;
    if (vis[p].count(s))
        return INF;
    vis[p].insert(s);
    ll ans = INF;
    int ls = s.size();
    for (int i = 0; i < n; ++i) {
        string t  = S[!p][i];
        int lt    = t.size();
        int l     = min(ls, lt);
        string ps = s.substr(0, l);
        string pt = t.substr(0, l);
        if (ps != pt)
            continue;
        ll cost =
            ls > lt ? dfs(s.substr(l, ls - l), p) : dfs(t.substr(l, lt - l), !p);
        if (cost < ans)
            ans = min(ans, cost + C[i]);
    }
    vis[p].erase(s);
    memo[p][s] = ans;
    return ans;
}

int main() {
    cin >> n;
    S[0]   = vector<string>(n);
    S[1]   = vector<string>(n);
    C      = vector<ll>(n);
    ll ans = INF;
    for (int i = 0; i < n; ++i) {
        cin >> S[0][i] >> C[i];
        S[1][i] = string(S[0][i].rbegin(), S[0][i].rend());
    }
    for (int i = 0; i < n; ++i)
        ans = min(ans, dfs(S[0][i], 0) + C[i]);
    cout << (ans == INF ? -1 : ans);
}
相關文章
相關標籤/搜索