ACwing提高课-DP(数字三角形,最长上升子序列)

本文详细讲解了数字三角形模型中的acwing1027方格取数问题的动态规划求解方法,以及最长上升子序列的两种常见算法:基础O(n^2)和贪心策略。还涉及导弹拦截和防御系统的解决方案,展示了递归和贪心在这些问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、数字三角形模型

1、acwing1027.方格取数

  1. 题目大意:在网格图中,从左上角到右下角走2遍,所取的的价值的最大。
  2. 题解:
    • 与普通的数字三角形DP不同的是,这里有4中状态,两步分别的状态为:下下、下右、右下、右右。
    • D P [ i ] [ j ] [ k ] DP[i][j][k] DP[i][j][k]:k表示(x+y)的值,即表示走的距离。i表示第一次走到x位置,j表示第二次走到j位置。
    • DP方程如code中
  3. ACcode
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, a, n) for (int i = n; i >= a; i--)
#define lowbit(x) ((x) & -(x))
#define lson p << 1, l, mid
#define rson p << 1 | 1, mid + 1, r
#define mem(a, b) memset(a, b, sizeof(a))
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0)
const double PI = acos(-1.0);
const ll mod = 1e9 + 7;
ll powmod(ll a, ll b)
{
    ll res = 1;
    a %= mod;
    assert(b >= 0);
    for (; b; b >>= 1)
    {
        if (b & 1)
            res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
template <class T>
inline void read(T &res)
{
    char c;
    T flag = 1;
    while ((c = getchar()) < '0' || c > '9')
        if (c == '-')
            flag = -1;
    res = c - '0';
    while ((c = getchar()) >= '0' && c <= '9')
        res = res * 10 + c - '0';
    res *= flag;
}
int dp[15][15][36];
int a[17][17];
int main()
{
    IOS;
    int n;
    cin >> n;
    int x, y, z;
    while (cin >> x >> y >> z)
    {
        if (x == y && y == z && z == 0)
            break;
        a[x][y] = z;
    }
    for (int k = 2; k <= n + n; k++)
    {
        for (int i1 = 1; i1 <= n; i1++)
        {
            for (int i2 = 1; i2 <= n; i2++)
            {
                int j1 = k - i1, j2 = k - i2;
                if (j1 >= 1 && j1 <= n && j2 >= 1 && j2 <= n)
                {
                    int w = a[i1][j1];
                    if (i1 != i2)
                        w += a[i2][j2];
                    dp[i1][i2][k] = max(dp[i1][i2][k], dp[i1 - 1][i2 - 1][k - 1] + w); //下下
                    dp[i1][i2][k] = max(dp[i1][i2][k], dp[i1][i2 - 1][k - 1] + w);     //右下
                    dp[i1][i2][k] = max(dp[i1][i2][k], dp[i1 - 1][i2][k - 1] + w);     //下右
                    dp[i1][i2][k] = max(dp[i1][i2][k], dp[i1][i2][k - 1] + w);         //右右
                }
            }
        }
    }
    cout << dp[n][n][2 * n];
    return 0;
}

二、最长上升子序列

1、基础的最长上升子序列O(n^2)

for(int i = 1; i <= n ; i++){
	dp[i] = 1;
	int maxx = 0;
	for(int j = 1; j < i ; j++){
		if(a[i] > a[j]) maxx = max(maxx,dp[j]);
	}
	dp[i] += dp[j];
}

2、导弹拦截

  1. 如何输出有几个单调子序列?
    • 贪心:找到第一个大于等于a[i]的栈,并将a[i]放入当前栈中。
    • 若没有找到,则新开辟一个栈,将其存入。
  2. ACcode
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, a, n) for (int i = n; i >= a; i--)
#define lowbit(x) ((x) & -(x))
#define lson p << 1, l, mid
#define rson p << 1 | 1, mid + 1, r
#define mem(a, b) memset(a, b, sizeof(a))
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0)
const double PI = acos(-1.0);
const ll mod = 1e9 + 7;
ll powmod(ll a, ll b)
{
    ll res = 1;
    a %= mod;
    assert(b >= 0);
    for (; b; b >>= 1)
    {
        if (b & 1)
            res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
template <class T>
inline void read(T &res)
{
    char c;
    T flag = 1;
    while ((c = getchar()) < '0' || c > '9')
        if (c == '-')
            flag = -1;
    res = c - '0';
    while ((c = getchar()) >= '0' && c <= '9')
        res = res * 10 + c - '0';
    res *= flag;
}
const int N = 1003;
int a[N];
int dp[N];
int ans1, ans2;
int n;
int g[N];
int main()
{
    IOS;
    int x;
    while (cin >> x)
        a[++n] = x;
    for (int i = 1; i <= n; i++)
    {
        dp[i] = 1;
        int maxx = 0;
        for (int j = 1; j < n; j++)
        {
            if (a[i] <= a[j])
                maxx = max(dp[j], maxx);
        }
        dp[i] += maxx;
        ans1 = max(ans1, maxx);
    }
    int cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        int idx = 0;
        while (idx < cnt && g[idx] < a[i]) //找到第一个小于g的数
            idx++;
        g[idx] = a[i];
        if (idx >= cnt)
            cnt++;
    }
    cout << ans1 << "\n"
         << cnt;
    return 0;
}

3、导弹防御系统

  1. 问题:问该序列中,最少有几个上升+下降子序列,将整个序列覆盖?
  2. 暴力:对于每一个数a[i],有两种方案,将其当做上升子序列的一部分、或者是下降子序列的一部分,对其dfs即可。
  3. ACcode
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, a, n) for (int i = n; i >= a; i--)
#define lowbit(x) ((x) & -(x))
#define lson p << 1, l, mid
#define rson p << 1 | 1, mid + 1, r
#define mem(a, b) memset(a, b, sizeof(a))
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0)
const int N = 55;
int a[N];
int up[N], down[N];
int ans;
int n;

void dfs(int u, int su, int sd)
{
    if (su + sd >= ans)
        return;
    if (u == n + 1)
    {
        ans = su + sd;
        return;
    }
    //上升子序列
    int k = 0;
    while (k < su && a[u] <= up[k])
        k++;
    int t = up[k]; //回溯
    up[k] = a[u];
    if (k < su)
        dfs(u + 1, su, sd);
    else //新开了
        dfs(u + 1, su + 1, sd);
    up[k] = t;
    
    //下降
    k = 0;
    while (k < sd && a[u] >= down[k])
        k++;
    t = down[k];
    down[k] = a[u];
    if (k < sd)
        dfs(u + 1, su, sd);
    else
        dfs(u + 1, su, sd + 1);
    down[k] = t;

}
int main()
{
    IOS;

    while (cin >> n && n)
    {
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        ans = n;
        dfs(1, 0, 0);
        cout << ans << "\n";
    }

    return 0;
}

4、最长公共上升子序列

  1. 方法:
    • d p [ i ] [ j ] dp[i][j] dp[i][j]:a中前i个数字,b中前j个数字,且以b[j]为结尾的子序列长度最大
    • 状态转移:
      • 不选择a[i], d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j ] ) dp[i][j] = max(dp[i][j],dp[i-1][j]) dp[i][j]=max(dp[i][j],dp[i1][j])
      • 选择a[i],且 a i = b j , b j > b k , d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ k ] + 1 ) a_i=b_j,b_j>b_k,dp[i][j] = max(dp[i][j],dp[i-1][k]+1) ai=bj,bj>bk,dp[i][j]=max(dp[i][j],dp[i1][k]+1)
    • 状态优化:
      • 对于第二种,状态,每次都是使用i-1的状态转移,可以存储 d p [ i − 1 ] [ j ] + 1 dp[i-1][j]+1 dp[i1][j]+1的最大值。
  2. ACcode
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, a, n) for (int i = n; i >= a; i--)
#define lowbit(x) ((x) & -(x))
#define lson p << 1, l, mid
#define rson p << 1 | 1, mid + 1, r
#define mem(a, b) memset(a, b, sizeof(a))
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0)
const double PI = acos(-1.0);
const ll mod = 1e9 + 7;
ll powmod(ll a, ll b)
{
    ll res = 1;
    a %= mod;
    assert(b >= 0);
    for (; b; b >>= 1)
    {
        if (b & 1)
            res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}
ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; }
template <class T>
inline void read(T &res)
{
    char c;
    T flag = 1;
    while ((c = getchar()) < '0' || c > '9')
        if (c == '-')
            flag = -1;
    res = c - '0';
    while ((c = getchar()) >= '0' && c <= '9')
        res = res * 10 + c - '0';
    res *= flag;
}
const int N = 3005;
int dp[N][N]; //前i和j个字母,以b[j]结尾的LICS
int a[N], b[N];
int main()
{
    IOS;
    int n;
    cin >> n;
    rep(i, 1, n) cin >> a[i];
    rep(j, 1, n) cin >> b[j];
    for (int i = 1; i <= n; i++) //a[i]
    {
        int maxx = 1;
        for (int j = 1; j <= n; j++) //b[j]
        {
            dp[i][j] = dp[i - 1][j]; //不选取a[i]
            if (a[i] == b[j])        //选a[i]
            {
                dp[i][j] = max(dp[i][j], maxx);
            }
            if (b[j] < a[i])
                maxx = max(maxx, dp[i][j] + 1);
        }
    }
    int ans = 0;
    rep(i, 1, n)
        ans = max(ans, dp[n][i]);
    cout << ans;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值