AtCoder Educational DP Contest

A - Frog 1

大意

n块石头,第i块石头的高度为h_i。从石头i跳到石头j的花费是|h_i-h_j|

一只青蛙在石头1上,每次可以跳1步或2步,请问跳到石头n的最小代价是多少?

2 \le n \le 10^5, 1 \le h_i \le 10^4

思路

\operatorname{cost}(i,j)=|h_i-h_j|dp_i为青蛙跳到第i号石头时的最小代价。

每一个点都可以由前两个点转移而来,因此状态转移方程为:

dp_i=\min(dp_{i-1}+\operatorname{cost}(i-1,i), dp_{i-2}+\operatorname{cost}(i-2, i))

边界可由定义得出:dp_1=0,dp_2=\operatorname{cost}(1,2)

时间复杂度O(n)

代码

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;
#define int long long
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n;
    cin >> n;

    vector<int> a(n), dp(n, 0);
    for(auto &i: a) cin >> i;

    auto cost = [&](int i, int j) -> int{
        return abs(a[i] - a[j]);
    };

    dp[1] = cost(0, 1);
    for(int i = 2; i < n; i++)
        dp[i] = min(dp[i - 1] + cost(i, i - 1), dp[i - 2] + cost(i, i - 2));
    cout << dp.back() << endl;
    return 0;
}

B - Frog 2

大意

n块石头,第i块石头的高度为h_i。从石头i跳到石头j的花费是|h_i-h_j|

一只青蛙在石头1上,每次可以跳1~k步,请问跳到石头n的最小代价是多少?

2 \le n \le 10^5, 1 \le k \le 100, 1 \le h_i \le 10^4

思路

和上一题一样,设\operatorname{cost}(i,j)=|h_i-h_j|dp_i为青蛙跳到第i号石头时的最小代价。

每一个点都可以由前k个点转移而来(除了i < k的情况),因此状态转移方程为:

dp_i=\min(dp_{i-1}+\operatorname{cost}(i-1,i), dp_{i-2}+\operatorname{cost}(i-2, i), \cdots, dp_{\max(1, i - k)} + \operatorname{cost}(\max(1, i - k), i))

边界为dp_1=0

时间复杂度O(nk)

代码

#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

#define int long long
const int INF = 0x3f3f3f3f;

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int n, k;
    cin >> n >> k;
    vector<int> a(n), dp(n, INF);
    for(auto &i: a) cin >> i;

    auto cost = [&](int i, int j){
        return abs(a[i] - a[j]);
    };

    dp[0] = 0;
    for(int i = 1; i < n; i++){
        for(int j = max(0LL, i - k); j < i; j++) dp[i] = min(dp[i], dp[j] + cost(i, j));
    }
    cout << dp.back() << endl;
    return 0;
}

C - Vacation

大意

n天时间和3种活动。每天只能进行一个活动,且相邻两天不能做相同的活动。

i天做三种活动分别可以获得a_i,b_i,c_i点快乐值,求这n天里最大可以获得多少点快乐值。

1 \le n \le 10^5, 1 \le a_i,b_i,c_i \le 10^4

思路

dp_{i,j}为考虑到第i天,且第i天进行活动j的最大值。

由于相邻两天不能做相同的活动,所以dp_{i,j}只能从另外两个状态中的最大值转移过来。、

因此,状态转移方程为:

\begin{cases} dp_{i,1}=\max(dp_{i-1,2}, dp_{i-1,3}) + a_i \\ dp_{i,2}=\max(dp_{i-1,1}, dp_{i-1,3}) + b_i \\ dp_{i,3}=\max(dp_{i-1,1}, dp_{i-1,2}) + c_i \end{cases}

边界为dp_{0,1}=dp_{0,2}=dp_{0,3}=0,答案为\max(dp_{n,1}, dp_{n,2}, dp_{n,3})

时间复杂度O(n)

代码

#include <iostream>
#include <vector>
using namespace std;
#define int long long

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    
    int n;
    cin >> n;

    vector<int> a(n), b(n), c(n);
    vector<int> dp1(n, 0), dp2(n, 0), dp3(n, 0);
    for(int i = 0; i < n; i++) cin >> a[i] >> b[i] >> c[i];

    dp1[0] = a[0], dp2[0] = b[0], dp3[0] = c[0];
    for(int i = 1; i < n; i++){
        dp1[i] = max(dp2[i - 1], dp3[i - 1]) + a[i];
        dp2[i] = max(dp1[i - 1], dp3[i - 1]) + b[i];
        dp3[i] = max(dp1[i - 1], dp2[i - 1]) + c[i];
    }
    cout << max(dp1.back(), max(dp2.back(), dp3.back())) << endl;

    return 0;
}

D - Knapsack 1

大意

n件物品和一个承重量为m千克的袋子。

i件物品的重量为w_i千克,价值为v_i元。

现在要挑选一些物品装入袋子里,求选择的物品的最大总价值。

1 \le n\le 100, 1 \le m \le 10^5, 1 \le w_i \le m, 1 \le v_i \le 10^9

思路

经典01背包问题,按照01背包的解法做即可。

注意需要long long。

时间复杂度O(nm)

代码

#include <iostream>
#include <vector>
using namespace std;
#define int long long

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    int n, m;
    cin >> n >> m;

    vector<int> w(n), v(n), dp(m + 1, 0);
    for(int i = 0; i < n; i++) cin >> w[i] >> v[i];

    for(int i = 0; i < n; i++)
        for(int j = m; j >= w[i]; j--)
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
    
    cout << dp[m] << endl;
    return 0;
}

E - Knapsack 2

大意

n件物品和一个承重量为m千克的袋子。

i件物品的重量为w_i千克,价值为v_i元。

现在要挑选一些物品装入袋子里,求选择的物品的最大总价值。

1 \le n\le 100, 1 \le m \le 10^9, 1 \le w_i \le m, 1 \le v_i \le 10^3

思路

w_i很大,直接套用01背包计算,时间和空间肯定一定炸。

由于v_i很小,我们可以转换思路,设dp_{j}为价值为j 的最小重量,这样就不会炸了。

最后从大到小遍历j,找到符合dp_j \le m的直接输出j

时间复杂度O(ns),其中sj的取值范围,s=\sum v_i

代码

#include <iostream>
#include <vector>
using namespace std;
#define int long long
const int INF = 0x3f3f3f3f;

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    
    int n, m;
    cin >> n >> m;

    vector<int> w(n), v(n);
    int sum = 0;
    for(int i = 0; i < n; i++){
        cin >> w[i] >> v[i];
        sum += v[i];
    }

    vector<int> dp(sum + 1, INF);
    dp[0] = 0;
    
    for(int i = 0; i < n; i++)
        for(int j = sum; j >= v[i]; j--)
            dp[j] = min(dp[j], dp[j - v[i]] + w[i]);
    
    for(int j = sum; j >= 0; j--)
        if(dp[j] <= m){
            cout << j << endl;
            break;
        }
    return 0;
}

F - LCS

大意

给定两个字符串,输出它们的任意一个最长公共子序列。

思路

求LCS时将最优策略保存下来,用x_{i,j}来表示状态(i,j)是否和(i-1,...)有关,y_{i,j}来表示状态(i,j)是否和(...,j-1)有关。

根据xy,就可以递归输出方案。

代码

#include <iostream>
#include <vector>
using namespace std;
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    string x, y;
    cin >> x >> y;
    int n = x.size(), m = y.size();

    vector<vector<int>> dp(n + 1, vector<int>(m + 1));
    vector<vector<int>> f1(n + 1, vector<int>(m + 1));
    vector<vector<int>> f2(n + 1, vector<int>(m + 1));

    auto lcs = [&](string a, string b, int n, int m) -> void{
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++){
                if(a[i - 1] == b[j - 1]){
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    f1[i][j] = 1;
                    f2[i][j] = 1;
                }else if(dp[i - 1][j] > dp[i][j - 1]){
                    dp[i][j] = dp[i - 1][j];
                    f1[i][j] = 1;
                }else{
                    dp[i][j] = dp[i][j - 1];
                    f2[i][j] = 1;
                }
            }
    };

    string ans = "";
    auto dfs = [&](auto self, string a, string b, int i, int j) -> void{
        if(i == 0 || j == 0) return;
        self(self, a, b, i - f1[i][j], j - f2[i][j]);
        if(a[i - 1] == b[j - 1]) ans.push_back(a[i - 1]);
    };

    lcs(x, y, n, m);
    dfs(dfs, x, y, n, m);
    cout << ans << endl;
    
}

G - Longest Path

大意

给定一个DAG,求其最长链上的边数量。

思路

DP求解,对每个点进行记忆化搜索,求出每个点为起点的最长链。

最后取个max即可。

代码

#include <iostream>
#include <vector>
using namespace std;
#define int long long
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;

    vector<vector<int>> G(n);
    vector<int> dp(n, 0);

    while(m--){
        int u, v;
        cin >> u >> v;
        u--, v--;
        G[u].push_back(v);
    }

    auto dfs = [&](auto self, int u) -> int{
        if(dp[u] == 0)
            for(int v : G[u]) dp[u] = max(dp[u], self(self, v) + 1);
        return dp[u];
    };

    int ans = 0;
    for(int i = 0; i < n; i++) ans = max(ans, dfs(dfs, i));
    cout << ans << endl;

    return 0;
}

  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值