• AtCoder Beginner Contest 175 (AB水,C数学,D思维+前缀和处理+进价思考,E方阵+条件DP,F新回文字符串处理 GJ)


    补题链接:Here

    A - Rainy Season

    如果不是 RSR 型的话直接计算 R 的数量即可

    B - Making Triangle

    给定 (N) 根长度分别为 (L_i) 的棍子,问能组成多少个三边长度各不相同的三角形?如果两个三角形至少用了一根不同编号的棍子,则称它们是不同的三角形。

    由于数据范文较小 ((N le 100)),所以我们可以排序以后枚举三元组即可。

    另外 CP wiki 提到这里进一步优化的话,可以在固定最长边的基础上,用双指针确定另外两条边的长度范围,这样时间复杂度就降到的了 (mathcal{O}(N^2))

    C - Walking Takahashi

    题意:有一个数 (X) ,对它进行 (K)(+D)(−D) 的操作,求操作后的 (min|X'|)

    思路:

    首先XX的正负不影响结果,所以我们可以只考虑 (|X|)

    如果 (|X|>D),那么我们首先应该向原点移动,直到 (|X'|<D)。这时还剩下 (K′) 次操作,我们应当在原点的左右两侧来回移动。根据 (K′) 的奇偶判断一下最后在哪一个位置即可。

    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(Nleq5000))个方格,从第 (i) 个方格会跳到第 (P_i) 个方格。PP是 (1,cdots,N)的一个排列。

    每个方格上写了一个数字 (C_i) 。每次跳跃时,会得到等同于 (C_{P_i}) 的分数。你可以从任意方格开始,跳跃至少一次,至多 (K) 次,求能够取得的最高分数。

    思路:枚举起点。由于 (P) 是排列,所以我们从任意位置 (i) 开始,经过若干次跳跃后一定会回到 (i) 。我们可以计算出一个周期内的前缀和。然后,根据周期长度 (C)(K) 之间的关系,分情况讨论。

    • (Kleq 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 << "
    ";
        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 << "
    ";
        return 0;
    }
    

    F - Making Palindrome

    F 题是懵逼ing

    (N)(Nleq50))个长度不超过 (L)(Lleq20))的字符串,每个字符串可以使用无限次,第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);
    }
    

    The desire of his soul is the prophecy of his fate
    你灵魂的欲望,是你命运的先知。

  • 相关阅读:
    org.eclipse.core.internal.registry.ExtensionsParser的流程分析
    控件设计时的属性页定位办法
    IE的内存泄漏问题
    <<深入剖析ASP.NET组件设计>>的一个辅助类
    midp2.0 图片文件名问题
    解决了XMLTextReader的用法的一个难题,EndElement
    J2ME开发心得
    解决Js内存泄漏问题的代码片断
    asp.net代码中尖括号和百分号的含义
    string.Format和cookie代码
  • 原文地址:https://www.cnblogs.com/RioTian/p/14635025.html
Copyright © 2020-2023  润新知