美文网首页算法分析
【算法分析】——区间dp

【算法分析】——区间dp

作者: 奈何缘浅wyj | 来源:发表于2020-11-25 09:45 被阅读0次

    所谓区间dp,指在一段区间上进行动态规划,一般做法是由长度较小的区间往长度较大的区间进行递推,最终得到整个区间的答案,而边界就是长度为1以及2的区间。

    转移方程

    区间dp常见的转移方程如下:

    dp(i,j) = min{dp(i,k-1) + dp(k,j)} + w(i,j)   (i < k <= j)
    

    其中dp(i,j)表示在区间[i,j]上的最优值,w(i,j)表示在转移时需要额外付出的代价,min也可以是max。

    四边形不等式

    按上述转移方程递推的时间复杂度为O(n3),如果w函数满足区间单调性和四边形不等式,可通过四边形不等式将时间优化到O(n2)。

    • 区间单调性:对于a<=b<=c<=d,有w(b,c) <= w(a,d)
    • 四边形不等式:对于a<=b<=c<=d,有f[a][c]+f[b][d]<=f[a][d]+f[b][c]

    w满足以上两点时,dp也满足四边形不等式,定义s(i,j)表示dp(i,j)取得最优值时对应的下标,那么有s(i,j)单调,即s(i,j)<=s(i,j+1)<=s(i+1,j)

    将该单调性应用到转移方程中,可大大缩小k的枚举范围。

    dp(i,j) = min{dp(i,k-1) + dp(k,j)} + w(i,j)    (s(i,j-1) <= k <= s(i+1,j))
    

    石子归并

    题面: N(<=100)堆石子摆成一条线,要将石子合并,每次只能选相邻的两堆合并成新的一堆,并将新堆的石子数记为该次操作的代价,求将所有堆合并为一堆的最小代价。

    #include <bits/stdc++.h>
    using namespace std;
    int n, a[105], p[105], dp[105][105];
    int sum(int l, int r) {
        return p[r] - p[l-1];
    }
    void go() {
        for (int i = 1; i <= n; i++)
            dp[i][i] = 0;
        for (int l = 2; l <= n; l++)
        for (int i = 1; i <= n; i++) {
            int j = i+l-1;
            if (j > n) break;
            dp[i][j] = INT_MAX;
            for (int k = i; k < j; k++)
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + sum(i,j));
        }
    }
    int main() {
        cin >> n;
        for (int i = 1; i <= n; i++) {
            cin >> a[i];
            p[i] = a[i] + p[i-1];
        }
        go();
        cout << dp[1][n] << endl;
        return 0;
    }
    

    石子归并V2

    题面: 与上一题类似,但石堆改成了环状,且N<=1000。

    分析: 将石堆复制一份到原石堆后面,把环状问题转化为线状,然后用四边形不等式降低时间复杂度。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const LL maxn = 2000;
    LL n, N, a[maxn+5], p[maxn+5], dp[maxn+5][maxn+5], ss[maxn+5][maxn+5];
    LL sum(LL l, LL r) {
        return p[r] - p[l-1];
    }
    void go() {
        for (LL i = 1; i <= N; i++) {
            dp[i][i] = 0;
            ss[i][i] = i;
        }
        for (LL l = 2; l <= n; l++)
        for (LL i = 1; i <= N; i++) {
            LL j = i+l-1;
            if (j > N) break;
            dp[i][j] = LLONG_MAX;
            for (LL k = ss[i][j-1]; k <= ss[i+1][j]; k++) {
                LL t = dp[i][k] + dp[k+1][j] + sum(i,j);
                if (dp[i][j] > t) {
                    dp[i][j] = t;
                    ss[i][j] = k;
                }
            }
        }
    }
    int main() {
        cin >> n;
        for (LL i = 1; i <= n; i++) {
            cin >> a[i];
            a[i+n] = a[i];
        }
        N = 2*n;
        for (LL i = 1; i <= N; i++)
            p[i] = a[i] + p[i-1];
        go();
        LL ans = LLONG_MAX;
        for (LL i = 1; i <= n; i++)
            ans = min(ans, dp[i][i+n-1]);
        cout << ans << endl;
        return 0;
    }
    

    Brackets sequence

    题面: 给出一串由"(",")","[","]"组成的字符串,要求往其中添加最少的括号,使得整个串为合法括号序列,并打印任意一组可行解。

    分析:dp[i][j]表示区间[i,j]需添加的最少括号数,递推时需要注意类似"[][]"的情形。

    #include <bits/stdc++.h>
    using namespace std;
    string s;
    int T, dp[105][105];
    int match(int x, int y) {
        return (s[x] == '(' && s[y] == ')')
            || (s[x] == '[' && s[y] == ']');
    }
    void DP(int len) {
        for (int i = 0; i < len; i++)
            dp[i][i] = 1;
        for (int i = 0; i+1 < len; i++)
            dp[i][i+1] = match(i,i+1) ? 0 : 2;
        for (int l = 2; l < len; l++)
        for (int i = 0; i < len; i++) {
            int j = i+l;
            if (j >= len) break;
            if (match(i,j))
                dp[i][j] = dp[i+1][j-1];
            else
                dp[i][j] = j-i+1;
            for (int k = i; k < j; k++)
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j]);
        }
    }
    void print(int x, int y) {
        if (x > y) return;
        if (x == y) {
            if (s[x] == '(' || s[x] == ')')
                printf("()");
            else
                printf("[]");
            return;
        }
        if (match(x,y) && dp[x][y] == dp[x+1][y-1]) {
            printf("%c", s[x]); print(x+1, y-1); printf("%c", s[y]);
            return;
        }
        for (int k = x; k < y; k++) {
            if (dp[x][y] == dp[x][k] + dp[k+1][y]) {
                print(x, k); print(k+1, y);
                return;
            }
        }
    }
    int main() {
        cin >> T; getline(cin, s);
        for (int kase = 1; kase <= T; kase++) {
            getline(cin, s); getline(cin, s);
            if (kase > 1) cout << endl;
            if (s.length() == 0) {
                cout << "";
            } else {
                DP(s.length());
                print(0, s.length() - 1);
            }
            cout << endl;
        }
        return 0;
    }
    

    Palindrome subsequence

    题面: 给定字符串s,问它包含多少个回文子序列?输出结果对10007取余即可。

    分析:dp[i][j]表示区间[i,j]的答案,根据容斥原理进行递推即可。

    #include <bits/stdc++.h>
    using namespace std;
    string s;
    int T, dp[1005][1005], mod = 10007;
    void DP(int len) {
        for (int i = 0; i < len; i++)
            dp[i][i] = 1;
        for (int i = 0; i+1 < len; i++)
            dp[i][i+1] = s[i] == s[i+1] ? 3 : 2;
        for (int l = 2; l < len; l++)
        for (int i = 0; i < len; i++) {
            int j = i+l;
            if (j >= len) break;
            dp[i][j] = dp[i][j-1] + dp[i+1][j] - dp[i+1][j-1];
            if (s[i] == s[j])
                dp[i][j] += dp[i+1][j-1] + 1;
            dp[i][j] = (dp[i][j] % mod + mod) % mod;
        }
    }
    int main() {
        scanf("%d", &T);
        for (int kase = 1; kase <= T; kase++) {
            cin >> s;
            DP(s.length());
            cout << "Case " << kase << ": " << dp[0][s.length()-1] << endl;
        }
        return 0;
    }
    

    资源传送门

    • 关注【做一个柔情的程序猿】公众号
    • 在【做一个柔情的程序猿】公众号后台回复 【python资料】【2020秋招】 即可获取相应的惊喜哦!

    「❤️ 感谢大家」

    • 点赞支持下吧,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)
    • 欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程

    相关文章

      网友评论

        本文标题:【算法分析】——区间dp

        本文链接:https://www.haomeiwen.com/subject/hevgiktx.html