算法提高-动态规划-区间DP

AcWing 1068. 环形石子合并【区间DP+环形区间问题】

最经典的区间dp问题,先枚举len再枚举左右端点,这样的原因是可以吧每个状态都表示出来不漏掉每一个状态

#include <iostream>
#include <cstring>
const int N = 400 + 10, INF = 0x3f3f3f3f;//因为是环形我们把它拓展成一个链了
int g[N][N], f[N][N];
int w[N], s[N];
int n;

void solve()
{
    
    
    //input
    std::cin >> n;
    for(int i = 1; i <= n; ++ i) std::cin >> w[i], w[i + n] = w[i];
    
    //预处理前缀和,快速获得一个区间内的石子被合并的得分
    //从这里我们可以发现,我们处理的前缀和是这样的:s[r] - s[l] = w[l + 1] + w[l + 2] + ... + w[r]
    for (int i = 1; i <= n << 1; ++ i) s[i] = s[i - 1] + w[i];
    
    //dp, 
    //dp初始化
    memset(f, 0x3f, sizeof f);
    memset(g, - 0x3f, sizeof g);
    for (int len = 1; len <= n; ++ len)
        //for (int l = 1, r = l + len - 1; r <= n << 1; ++ l) 会segemention fault
        for (int l = 1, r; r = l + len - 1, r <= n << 1; ++ l)
        {
    
    
            if (len == 1) f[l][l] = g[l][l] = 0;//如果长度为1就用来初始化
            else 
            {
    
    //如果长度不为1
                for (int k = l; k + 1 <= r; ++ k)
                {
    
    
                    f[l][r] = std::min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
                    g[l][r] = std::max(g[l][r], g[l][k] + g[k + 1][r] + s[r] - s[l - 1]);
                }
            }
        }
    
    int minV = INF, maxV = -INF;
    for (int l = 1; l <= n; ++ l)
    {
    
    
        minV = std::min(minV, f[l][l + n - 1]);
        maxV = std::max(maxV, g[l][l + n - 1]);
    }
    std::cout << minV << std::endl << maxV;
    return ;
}

int main()
{
    
    
    solve();    
    return 0;
}

AcWing 320. 能量项链【区间DP】

本题代码模版就是区间dp的代码模版但是
在这里插入图片描述
这一段我说实话没看懂

#include <iostream>
#include <cstring>

const int N = 100, M = (N << 1) + 10, INF = 0x3f3f3f3f;

int w[M], f[M][M];

int n;
void solve()
{
    
    
    //input
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> w[i], w[i + n] = w[i];
    
    //dp
    memset(f, -0x3f, sizeof f);
    
    for (int len = 2; len <= n + 1; ++ len)
        for (int l = 1, r; (r = l + len - 1) <= n << 1; ++ l)
            if (len == 2) f[l][r] = 0;//本题可以不用初始化,因为不存在负数的状态值,因此0就是最小的
            else 
            {
    
    
                for (int k = l + 1; k + 1 <= r; ++ k)
                {
    
    
                    f[l][r] = std::max(f[l][r], f[l][k] + f[k][r] + w[l] * w[k] * w[r]);// f[l][k] + f[k][r] + w[l] * w[k] * w[r]可以看出为什么我们我们要遍历n + 1
                }
            }
    
    int ans = 0;
    for (int l = 1; l <= n; ++ l)
    {
    
    
        ans = std::max(ans, f[l][l + n]);
    }
    std::cout << ans;
    return ;
}

int main()
{
    
    
    solve();   
    return 0;
}

AcWing 479. 加分二叉树[记忆化搜索]

这题我们应该学会的是如何在dp决策(赋值)过程中记录我们的方案。
同时这题也可以用记忆化搜索的思想过掉,但是我赶进度就pass了,因为我还不知道啥是记忆化搜索

#include <iostream>

const int N = 31;

int f[N][N], g[N][N];
int w[N];
int n;

//这个前序遍历的输出就是要想像一下当前(l,r)是一个经过中序遍历过的序列,
//我们在dp的过程中用g[l][r]记录我们决策f[l][r]时 l - r区间的根结点,
//那么整棵树是以g[l][r]为根结点的,由中序遍历的特点,左边的树的区间就是 (l, g[l][r] - 1),右边的树的区间就是(g[l][r] + 1,  r)
void dfs(int l, int r)
{
    
    
    if (l > r) return ;
    else 
    {
    
    
        std::cout << g[l][r] << " ";
        dfs(l, g[l][r] - 1);
        dfs(g[l][r] + 1, r);
    }
}
void solve()
{
    
    
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> w[i];
    
    for (int len = 1; len <= n; ++ len)
    {
    
    
        for (int l = 1, r; l + len - 1 <= n; ++ l)
        {
    
    
            r = l + len - 1;
            if (len == 1) f[l][r] = w[l], g[l][r] = l;
            else 
            {
    
    
                for (int k = l; k < r; ++ k)
                {
    
       //  left表示左子树区间的值
                    int left = k == l ? 1 : f[l][k - 1];//根节点为k的时候如果根节点和枚举的这棵树的左区间左端点重合了,说明左子树为空
                    int right = k == r ? 1 : f[k + 1][r];
                    int score = left * right + w[k];
                    if (f[l][r] < score)
                    {
    
    
                        f[l][r] = score;
                        g[l][r] = k;
                    }
                }
            }
        }
    }
    
    std::cout << f[1][n] << std::endl;
    dfs(1, n);
    return ;
}

int main()
{
    
    
    solve();
    return 0;
}

AcWing 1069. 凸多边形的划分【区间dp + 高精度】

#include <iostream>
#include <vector>

using namespace std;

typedef long long LL;
const int N = 55;
int w[N];

vector<int> f[N][N];
int n;

bool cmp(vector<int> a, vector<int> b)
{
    
    
    if (a.size() != b.size()) return a.size() < b.size();
    else 
    {
    
    
        for (int i = a.size() - 1; i >= 0; -- i)//下面说了,这里的高精度模版全是反的,因此比较的时候也反着比
        {
    
    
            if (a[i] != b[i]) return a[i] < b[i];
        }
    }
    return true;
}

vector<int> add(vector<int> a, vector<int> b)
{
    
    
    vector<int> c;
    int t = 0;
    for (int i = 0; i < a.size() || i < b.size(); ++ i)
    {
    
    
        if (i < a.size()) t += a[i];
        if (i < b.size()) t += b[i];
        c.push_back(t % 10);
        t /= 10;
    }
    while (t) c.push_back(t % 10), t /= 10;
    return c;
}

vector<int> mul(vector<int> a, LL b)
{
    
    
    vector<int> c;
    LL t = 0;
    for (int i = 0; i < a.size(); ++ i)
    {
    
    
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }
    while (t) c.push_back(t % 10), t /= 10;
    return c;
}

void solve()
{
    
    
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> w[i];
    //dp;
    for (int len = 3; len <= n ; ++ len)
    {
    
    
        for (int l = 1, r; (r = l + len - 1) <= n; ++ l)
        {
    
    
            for (int k = l + 1; k < r; ++ k)//k从l + 1开始,因为l,k,r是三角形的三个顶点,肯定不能重合
            {
    
    
                auto newVal = mul(mul({
    
    w[l]}, w[k]), w[r]);
                newVal = add(add(newVal, f[l][k]), f[k][r]);
                if (f[l][r].empty() || cmp(newVal, f[l][r])) f[l][r] = newVal;
            }
        }
    }
    
    auto res = f[1][n];
    for (int i = res.size() - 1; i >= 0; --i) std::cout << res[i];//本题的高精度的模版全是反的,我就反着算,输出的时候也反转,负负得正
    return ;
}
int main()
{
    
    
    solve();
    return 0;
}

AcWing 321. 棋盘分割【记忆化搜索/二维区间DP】

这题遍历的时候下标从1开始,数组大小要开到9,不能是8,之前开8一直se,不过我好像知道了什么是记忆化搜索了,应该是剪枝的一种。

#include <iostream>
#include <cstring>
#include <cmath>


const int N = 16;

double f[N][9][9][9][9];
double X;//平均数
int s[9][9], n;

double get(int x1, int y1, int x2, int y2)
{
    
    
    double delta = s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
    delta -= X;
    return delta * delta;
}


double dp(int k, int x1, int y1, int x2, int y2)
{
    
    
    if (f[k][x1][y1][x2][y2] >= 0) return f[k][x1][y1][x2][y2]; //记忆化搜索
    if (k == n) return f[k][x1][y1][x2][y2] = get(x1, y1, x2, y2);  //更新初始状态

    double t = 1e9; //初始化为无穷大
    for (int i = x1; i < x2; i ++ ) //横着切
    {
    
    
        t = std::min(t, dp(k + 1, x1, y1, i, y2) + get(i + 1, y1, x2, y2));
        t = std::min(t, dp(k + 1, i + 1, y1, x2, y2) + get(x1, y1, i, y2));
    }
    for (int i = y1; i < y2; i ++ ) //竖着切
    {
    
    
        t = std::min(t, dp(k + 1, x1, y1, x2, i) + get(x1, i + 1, x2, y2));
        t = std::min(t, dp(k + 1, x1, i + 1, x2, y2) + get(x1, y1, x2, i));
    }
    return f[k][x1][y1][x2][y2] = t;
}



void solve()
{
    
    
    //input
    std::cin >> n;
    for (int i = 1; i <= 8; ++ i)
    {
    
    
        for (int j = 1; j <= 8; ++ j)
            std::cin >> s[i][j];
    }
    
    //预处理
    for (int i = 1; i <= 8; ++ i)
    {
    
    
        for (int j = 1; j <= 8; ++ j)
            s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
    }
    X = (double)(s[8][8]) / n;
    
    //dp
    memset(f, -1, sizeof f);
    double res = dp(1, 1, 1, 8, 8);
    //output
    printf("%.3lf", sqrt(res / n));
    return ;
}

int main()
{
    
    
    solve();
    return 0;
}

猜你喜欢

转载自blog.csdn.net/chirou_/article/details/131836272
今日推荐