zoukankan      html  css  js  c++  java
  • 双周赛 56 单周赛 249 部分题解

    知识点:图论,动态规划,字符串,状态压缩

    规定时间内到达终点的最小花费

    给定 (n) 个点,(m) 条边的双向图,每个点有点权 (p_{i}),边有边权,表示消耗的时间,现在要求计算出在指定时间 (t) 内,从点 (0) 到点 (n - 1) 的最小花费,花费值为路径上点的点权之和

    题解

    定义 (dp[i][j]) 表示消耗时间 (i),走到点 (j) 的最小花费

    枚举前继节点 (pre),设 (pre)(j) 的时间为 (w),那么状态转移方程为

    [dp[i][j] = minleft{dp[i][j], dp[i - w][pre] + p[j] ight} ]

    时间复杂度 (O(tcdot (m + n)))

    class Solution {
    public:
        #define pb push_back
        #define pii pair<int, int>
        #define fi first
        #define se second
        #define INF 0x3f3f3f3f
        const int N = 1e3 + 7;
        int minCost(int m, vector<vector<int>>& e, vector<int>& p) {
            int n = p.size();
            vector<vector<int>> dp(n, vector<int>(m + 7, INF));
            vector<pii> g[N];
            for (int i = 0; i < e.size(); ++i) {
                int x = e[i][0], y = e[i][1], z = e[i][2];
                g[x].pb({y, z});
                g[y].pb({x, z});
            }
            dp[0][0] = p[0];
            for (int k = 0; k <= m; ++k) {
                for (int i = 0; i < n; ++i) {
                    for (auto &j: g[i]) {
                        if (j.se + k <= m)
                            dp[j.fi][j.se + k] = min(dp[j.fi][j.se + k], dp[i][k] + p[j.fi]);
                    }
                }
            }
            int ans = INF;
            for (int i = 0; i <= m; ++i) if (dp[n - 1][i] != INF) cout << i << ' ' << dp[n - 1][i] << endl, ans = min(ans, dp[n - 1][i]);
            return ans == INF ? -1 : ans;
        }
    };
    

    无后效性

    注意,如果定义 (dp[i][j]) 表示走到点 (i),消耗时间为 (j) 的的最小花费,并按照先点后时间的顺序转移,计算的答案会出现错误

    例如 0 <-> 2 <-> 1 <-> 3 的路径,我们事实上应该按照 0 -> 2 -> 1 -> 3 的顺序转移,但我们实际上按照了 0 -> 1 -> 2 -> 3 的顺序转移,归根结底在于, 2 没有对 1 贡献

    也就是说,出现了 (pre > j),在计算 (j) 的时候,(pre) 还没计算出来

    回到无后效性的定义,当前状态不应该由后面的状态决定,因此上述的转移链不满足无后效性,但是如果第一维状态为时间,那么就满足了无后效性

    事实上,我们只能在 (DAG) 上按照拓扑关系进行 (dp),这样可以保证计算到 (j) 时,(pre) 一定已经被计算出来,从而满足了无后效性

    从表格的角度理解,当前行列只能由上一行或者当前行的前一列转移过来,不能由下一行或者当前行的后一列转移而来,当你的转移顺序不满足这个要求的时候,动态规划就会出错

    长度为 (3) 的不同回文子序列

    给定小写字母组成的字符串 (s),统计其中长度为 (3) 的不同的回文子序列个数

    规定 (3leq s.lengthleq 10^5)

    题解

    对于每一个字符 (i),我们找到它出现的第一个位置 (L) 和最后一个位置 (R),然后统计 ([L + 1, R - 1]) 之间有多少个不同的字符即可

    我们用一个二维的桶 (dict[i][j]) 来维护 ([1, i]) 中字符 (j) 出现的次数,然后便可以统计指定区间内不同字符的个数

    时间复杂度 (O(26cdot n + 26^2))

    class Solution {
    public:
        int countPalindromicSubsequence(string s) {
            int n = s.length();
            vector<int> L(26), R(26);
            vector<vector<int>> dict(n + 1, vector<int>(26));
            for (int i = 0; i < n; ++i)
                if (!L[s[i] - 'a']) L[s[i] - 'a'] = i + 1; // 'a' + i 第一次出现的位置
            for (int i = n - 1; i >= 0; --i)
                if (!R[s[i] - 'a']) R[s[i] - 'a'] = i + 1; // 'a' + i 最后一次出现的位置
            for (int i = 1; i <= n; ++i) {
                for (int j = 0; j < 26; ++j) {
                    dict[i][j] = dict[i - 1][j];
                }
                dict[i][s[i - 1] - 'a']++;
            }
            int ans = 0;
            for (int i = 0; i < 26; ++i) {
                int l = L[i], r = R[i], temp = 0;
                if (r && l && r > l + 1) {
                    for (int j = 0; j < 26; ++j) {
                        temp += (dict[r - 1][j] - dict[l][j] > 0);
                    }
                    //cout << l << ' ' << r << ' ' << temp << endl;
                    ans += temp;
                }
            }
            return ans;
        }
    };
    

    用三种不同颜色为网格涂色

    给定 (m imes n) 的网格,用 (RGB) 三种颜色染色,要求相邻网格颜色不重复,计算可能的染色方案数,答案对 (10^9 + 7) 取模

    (1leq mleq 5)

    (1leq nleq 1000)

    题解

    注意到 (m) 很小,可以考虑对列的染色情况进行状态压缩,一列一共有 (3^m) 种染色情况

    考虑状态压缩 (dp),定义 (dp[i][j]) 表示第 (i) 列,染色情况为 (j) 的方案数,那么状态转移方程为

    [dp[i][j] = sum{dp[i - 1][k]} ]

    其中 (k, j) 均为 (3) 进制数,并且满足每一位都不相同,且自身相邻位不相同

    时间复杂度 (O(ncdot 3^{2m})),使用哈希表预处理合法的列状态可以优化到 (O(ncdot 3^{m}))

    class Solution {
    public:
        const int MOD = 1e9 + 7;
        typedef long long LL;
        bool check_row(int m, int x, int y) {
            for (int i = 0; i < m; ++i) {
                if (x % 3 == y % 3) return 0;
                x /= 3, y /= 3;
            }
            return 1;
        }
        bool check_col(int m, int x) {
            int y = x / 3;
            for (int i = 1; i < m; ++i) {
                if (x % 3 == y % 3) return 0;
                x /= 3, y /= 3;
            }
            return 1;
        }
        int colorTheGrid(int m, int n) {
            LL tot = 1;
            for (int i = 1; i <= m; ++i) tot *= 3;
            vector<vector<LL>> dp(n + 1, vector<LL>(tot + 1));
            for (int i = 0; i < tot; ++i)
                if (check_col(m, i)) dp[1][i] = 1 % MOD;
            for (int i = 2; i <= n; ++i) {
                for (int j = 0; j < tot; ++j) {
                    if (!check_col(m, j)) continue;
                    for (int k = 0; k < tot; ++k) {
                        if (check_col(m, k) && check_row(m, j, k)) {
                            dp[i][j] += dp[i - 1][k], dp[i][j] %= MOD;
                        }
                    }
                }
            }
            LL ans = 0;
            for (int i = 0; i < tot; ++i) ans += dp[n][i], ans %= MOD;
            return ans;
        }
    };
    
  • 相关阅读:
    js获取当前时间日期
    js操作Cookie
    C#常用正则表达式
    jquery操作select、radio、checkbox表单元素
    js实现页面跳转的几种方式
    js获取页面宽高大小
    c++写一个类后编译发现class重定义
    vtkMultiThreader坑爹吗?
    vtkStandardNewMacro()出现错误的问题
    转:将CFormView嵌入到CDockablePane中
  • 原文地址:https://www.cnblogs.com/ChenyangXu/p/15059227.html
Copyright © 2011-2022 走看看