双周赛 56 单周赛 249 部分题解

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

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

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

题解

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

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

\[dp[i][j] = min\left\{dp[i][j],\ dp[i - w][pre] + p[j]\right\} \]

时间复杂度 \(O(t\cdot (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\) 的不同的回文子序列个数

规定 \(3\leq s.length\leq 10^5\)

题解

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

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

时间复杂度 \(O(26\cdot 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\times n\) 的网格,用 \(RGB\) 三种颜色染色,要求相邻网格颜色不重复,计算可能的染色方案数,答案对 \(10^9 + 7\) 取模

\(1\leq m\leq 5\)

\(1\leq n\leq 1000\)

题解

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

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

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

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

时间复杂度 \(O(n\cdot 3^{2m})\),使用哈希表预处理合法的列状态可以优化到 \(O(n\cdot 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;
    }
};
posted @ 2021-07-25 22:13  徐摆渡  阅读(34)  评论(0编辑  收藏  举报