5 动态规划

5 动态规划

5.1 常见线性模型
5.1.1 LIS(O(nlogn))

采用树状数组优化。

int c[maxn], n, r[maxn], a[maxn];
 
inline int lb(int x) { return x & -x; }
 
int query(int x) {
    int tot = 0;
    for (; x; x -= lb(x))
        tot = max(tot, c[x]);
    return tot;
}
 
void update(int x, int v) {
    for (; x <= n; x += lb(x)) {
        c[x] = max(c[x], v);
//        int t = x - lb(x);
//        for (int y = x - 1; y && y - lb(y) >= t; y -= lb(y))
//            c[x] = max(c[x], c[y]);
    }
}
 
const int cmp(const int i, const int j) {
    return a[i] == a[j] ? i < j : a[i] < a[j];
}
 
int main() {
    __;
    int _;
    cin >> _;
    while (_--) {
        cin >> n;
        for (int i = 1; i <= n; ++i) {
            cin >> a[i];
            r[i] = i;
        }
        sort(r + 1, r + 1 + n, cmp);
        memset(c, 0, sizeof c);
        int ans = 0;
        for (int j = 1; j <= n; ++j) {
            int i = r[j];
            int tmp = query(i - 1) + 1;
            update(i, tmp);
            ans = max(ans, tmp);
        }
        cout << ans << endl;
    }
    return 0;
}
5.1.2 LCS

复杂度O(nlogn)

#include <bits/stdc++.h>

using namespace std;

#define ll long long
#define ull unsigned long long
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)

const int maxn = 1e5 + 10;
int n, a[maxn], b[maxn], c[maxn];
int mp[maxn];

inline int lb(int x) { return x & -x; }

int query(int x) {
    int tot = 0;
    for (; x; x -= lb(x))
        tot = max(tot, c[x]);
    return tot;
}

void update(int x, int v) {
    for (; x <= n; x += lb(x)) {
        c[x] = max(c[x], v);
//        int t = x - lb(x);
//        for (int y = x - 1; y && y - lb(y) >= t; y -= lb(y))
//            c[x] = max(c[x], c[y]);
    }
}

int main() {
    __;
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    for (int i = 1; i <= n; ++i) {
        cin >> b[i];
        mp[b[i]] = i;
    }
    for (int i = 1; i <= n; ++i) b[i] = mp[a[i]];
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        int tmp = query(b[i] - 1) + 1;
        update(b[i], tmp);
        ans = max(ans, tmp);
    }
    cout << ans << endl;
    return 0;
}
5.1.3 LCIS

复杂度O(n*m)

int n, m;
int a[maxn], b[maxn];
int f[maxn][maxn];
 
int main() {
    __;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i];
    }
    for (int i = 1; i <= m; ++i) {
        cin >> b[i];
    }
    for (int i = 1; i <= n; ++i) {
        int tot = 0;
        for (int j = 1; j <= m; ++j) {
            f[i][j] = a[i] == b[j] ? tot + 1 : f[i - 1][j];
            if (b[j] < a[i])tot = max(tot, f[i - 1][j]);
        }
    }
    int ans = 0;
    for (int i = 1; i <= m; ++i)ans = max(ans, f[n][i]);
    cout << ans << endl;
    return 0;
}
5.1.4 石子合并问题
#include <bits/stdc++.h>

using namespace std;

#define ll long long
#define ull unsigned long long
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)

const int inf = 1000000000;
int a[201];
int f[201][201] = {0};
int sum[201] = {0};

int main() {
    __;
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
    }
    for (int i = 1; i <= n; i++) {
        a[i + n] = a[i];
        sum[i + n] = sum[i + n - 1] + a[i + n];
    }
    memset(f, 0, sizeof(f));
    for (int k = 1; k < n; k++) {
        for (int i = 1; i + k <= 2 * n; i++) {
            int r = i + k;
            f[i][r] = inf;
            for (int j = i; j < r; j++)f[i][r] = min(f[i][r], sum[r] - sum[i - 1] + f[i][j] + f[j + 1][r]);
        }
    }
    int tot = inf;
    for (int i = 1; i <= n; i++)tot = min(tot, f[i][i + n - 1]);
    cout << tot << endl;
    memset(f, 0, sizeof(f));
    for (int k = 1; k < n; k++) {
        for (int i = 1; i + k <= 2 * n; i++) {
            int r = i + k;
            f[i][r] = 0;
            for (int j = i; j < r; j++)f[i][r] = max(f[i][r], sum[r] - sum[i - 1] + f[i][j] + f[j + 1][r]);
        }
    }
    tot = 0;
    for (int i = 1; i <= n; i++)tot = max(tot, f[i][i + n - 1]);
    cout << tot;
    return 0;
}
5.3 树型dp
5.3.1 森林转二叉树

题意:给你n门课程,每门课有其先修课和学分。求选m门课程的最大学分。(森林转二叉树)

树形dp。关键是存树的方式。

在森林上找多个包含树根的连通块,使所有点的权值最大。

用二叉树存储,定义两个数组head和next,其中head[i]表示节点i的第一个儿子节点,next[i]表示节点i的兄弟节点。

若以知一个节点的父亲,则插入该节点的代码:

    if (head[fa] == 0)head[fa] = i;
    else {
        int t = head[fa];
        while (next[t] != 0)t = next[t];
        next[t] = i;
    }

dp方程:

f [ c ] [ s ] f[c][s] f[c][s] 表示在二叉树中以c为根的子树中取s个节点的最大权值,其中这s个节点均联通。

f [ c ] [ s ] = a [ c ] + m a x ( f [ h e a d [ c ] ] [ i ] , f [ n e x t [ c ] ] [ s − 1 − i ] ) ; f[c][s] = a[c] + max(f[head[c]][i], f[next[c]][s - 1 - i]); f[c][s]=a[c]+max(f[head[c]][i],f[next[c]][s1i]);

由于二叉树中节点c的右节点实际上与c是兄弟关系,故 f [ c ] [ s ] f[c][s] f[c][s]的值可以不包含节点c,故还应保证

f [ c ] [ s ] = m a x ( f [ c ] [ s ] , f [ n e x t [ c ] ] [ s ] ) ; f[c][s] = max(f[c][s], f[next[c]][s]); f[c][s]=max(f[c][s],f[next[c]][s]);

代码:

#include <bits/stdc++.h>

using namespace std;

#define ll long long
#define ull unsigned long long
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)

int a[1001];
int head[1001], nxt[1001];
int f[1001][1001];

int dp(int c, int s) {
    if (s == 0)return 0;
    if (c == 0)return 0;
    if (f[c][s] != 0)return f[c][s];
    for (int i = 0; i < s; i++)f[c][s] = max(f[c][s], a[c] + dp(head[c], i) + dp(nxt[c], s - 1 - i));
    f[c][s] = max(f[c][s], dp(nxt[c], s));
    return f[c][s];
}

int main() {
    __;
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++) {
        int fa;
        cin >> fa;
        if (head[fa] == 0)head[fa] = i;
        else {
            int t = head[fa];
            while (nxt[t] != 0)t = nxt[t];
            nxt[t] = i;
        }
        cin >> a[i];
    }
    cout << dp(head[0], m);
    return 0;
}
5.4 单调栈

手动实现版:

例题大意:给出N个矩形和他们的高和宽 现在把他们宽放在x轴上对齐,请问现在最大矩形面积。

int n;
struct Data {
    int left, right, height;
} s[maxn], a[maxn];

int main() {
    __;
    while (cin >> n) {
        if (n == -1)break;
        int l = 0, w, h;
        for (int i = 1; i <= n; ++i) {
            cin >> w >> h;
            a[i].height = h;
            a[i].left = l;
            l += w;
            a[i].right = l;
        }
        int ans = 0, top = 0;
        for (int i = 1; i <= n; ++i) {
            while (top && s[top].height > a[i].height) {
                Data p = s[top--];
                if (top == 0) {
                    ans = max(ans, p.height * (a[i].left));
                } else {
                    ans = max(ans, p.height * (a[i].left - s[top].right));
                }
            }
            Data x;
            x.left = a[i].left;
            x.right = a[i].right;
            x.height = a[i].height;
            s[++top] = a[i];
        }
        while (top) {
            Data p = s[top--];
            if (top == 0) {
                ans = max(ans, p.height * (a[n].right));
            } else {
                ans = max(ans, p.height * (a[n].right - s[top].right));
            }
        }
        cout << ans << endl;
    }
    return 0;
}

STL写法:
例题:给出直方图上n个矩形的高度,求最大面积。

stack<pair<ll, ll> > s;
ll n, a[maxn];

int main() {
    __;
    while (cin >> n) {
        if (n == 0)break;
        for (int i = 1; i <= n; ++i) {
            cin >> a[i];
        }
        ll ans = 0;
        for (ll i = 1; i <= n; ++i) {
            while (!s.empty() && s.top().first > a[i]) {
                pair<ll, ll> p = s.top();
                s.pop();
                if (s.empty()) {
                    ans = max(ans, p.first * (i - 1));
                } else {
                    ans = max(ans, (i - 1 - s.top().second) * p.first);
                }
            }
            s.push(pair<int, int>(a[i], i));
        }
        while (!s.empty()) {
            pair<ll, ll> p = s.top();
            s.pop();
            if (s.empty()) {
                ans = max(ans, n * p.first);
            } else {
                ans = max(ans, (n - s.top().second) * p.first);
            }
        }
        cout << ans << endl;
    }
    return 0;
}

不使用栈,直接用数组记录。

int n, m;
int a[2001][2001], h[2001][2001];
int l[2002][2001], r[2002][2002];

int main() {
    scanf("%d%d", &m, &n);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            scanf("%d", &a[i][j]);
            if (a[i][j]) {
                h[i][j] = 1;
                if (h[i - 1][j])h[i][j] += h[i - 1][j];
            } else h[i][j] = 0;
        }
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (h[i][j]) {
                int t = j - 1;
                l[i][j] = 1;
                while (t >= 1 && h[i][j] <= h[i][t]) {
                    l[i][j] += l[i][t];
                    t -= l[i][t];
                }
            }
        }
        for (int j = m; j >= 1; j--) {
            if (h[i][j]) {
                int t = j + 1;
                r[i][j] = 1;
                while (t <= m && h[i][j] <= h[i][t]) {
                    r[i][j] += r[i][t];
                    t += r[i][t];
                }
            }
        }
    }
    int tot = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            tot = max(tot, (l[i][j] + r[i][j] - 1) * h[i][j]);
        }
    }
    printf("%d\n", tot);
    return 0;
}
5.5 单调队列

手动实现版:

r - 2 * sum[r] - ((l - 1) - 2 * sum[l-1]) + x ,维护对于每个r,只需找出最小的((l - 1) - 2 * sum[l - 1])即可。

    for (int i = 1; i <= n; ++i)
        sum[i] = sum[i - 1] + c[i] - '0';
    while (m--) {
        int x;
        scanf("%d", &x);
        int l = 1, r = 0;
        int ans = 0;
        q[++r] = 0;
        for (int i = 1; i <= n; ++i) {
            while (l <= r && q[r] - sum[q[r]] * 2 > i - sum[i] * 2)
                --r;
            q[++r] = i;
            while (l <= r && sum[q[r]] - sum[q[l]] > x)
                ++l;
            ans = max(ans, q[r] - 2 * sum[q[r]] - (q[l] - 2 * sum[q[l]]) + x);
        }
    }

STL实现版:

ll sum[maxn], b[maxn];
list<int> q;

ll dp(int len, int cnt) {
    q.clear();
    ll ans = 0;
    for (int i = 1; i <= cnt; ++i) {
        sum[i] = sum[i - 1] + b[i];
    }
    for (int i = 1; i <= cnt; ++i) {
        while (!q.empty() && sum[q.front()] > sum[i])
            q.pop_front();
        q.push_front(i);
        while (!q.empty() && i - len > q.back())
            q.pop_back();
        if (i > 1)ans = max(ans, sum[i] - sum[q.back()]);
        else ans = max(ans, sum[i]);
    }
    return ans;
}
5.6 数位dp

Windy数:

const int maxn = 1e5 + 10;
const ll mod = 1e9 + 7;

int a[100];
ll f[101][11];

ll dp(int len) {
    if (len == 0)return 0;
    ll ans = 1;
    for (int i = 1; i < a[len]; ++i) {
        ans += f[len][i];
    }
    for (int i = len - 1; i; --i) {
        for (int j = 0; j < a[i]; ++j) {
            if (abs(j - a[i + 1]) >= 2)ans += f[i][j];
        }
        if (abs(a[i] - a[i + 1]) < 2) {
            ans--;
            break;
        }
    }
    for (int i = len - 1; i; --i) {
        for (int j = 1; j <= 9; ++j) {
            ans += f[i][j];
        }
    }
    return ans;
}

ll solve(ll t) {
    int pos = 0;
    while (t) {
        a[++pos] = (int) (t % 10);
        t /= 10;
    }
    return dp(pos);
}

int main() {
    __;
    for (int i = 0; i <= 9; ++i)
        f[1][i] = 1;
    for (int i = 2; i <= 15; ++i) {
        for (int j = 0; j <= 9; ++j) {
            for (int k = 0; k <= 9; ++k) {
                if (abs(j - k) >= 2) {
                    f[i][j] += f[i - 1][k];
                }
            }
        }
    }
    ll a, b;
    cin >> a >> b;
    cout << solve(b) - solve(a - 1) << endl;
    return 0;
}
5.7 四边形不等式优化

f [ i ] [ j ] = min ⁡ { f [ i ] [ k ] + f [ k + 1 ] [ j ] } + w [ i ] [ j ] f[i][j]=\min{\{f[i][k]+f[k+1][j]\}}+w[i][j] f[i][j]=min{f[i][k]+f[k+1][j]}+w[i][j]

  • w [ a ] [ c ] + w [ b ] [ d ] ≤ w [ b ] [ c ] + w [ a ] [ d ]   ( a < b < c < d ) w[a][c]+w[b][d]\leq w[b][c]+w[a][d]\ (a<b<c<d) w[a][c]+w[b][d]w[b][c]+w[a][d] (a<b<c<d)则称w满足四边形不等式
  • w满足四边形不等式,当且仅当 w [ i ] [ j ] + w [ i + 1 ] [ j + 1 ] ≤ w [ i + 1 ] [ j ] + w [ i ] [ j + 1 ] w[i][j]+w[i+1][j+1]\leq w[i+1][j]+w[i][j+1] w[i][j]+w[i+1][j+1]w[i+1][j]+w[i][j+1]
  • w [ a ] [ b ] < = w [ a ′ ] [ b ′ ]   ( [ a ,   b ] ⊂ [ a ′ ,   b ′ ] ) w[a][b]<=w[a'][b']\ ([a,\ b]\subset [a',\ b']) w[a][b]<=w[a][b] ([a, b][a, b])则称w关于区间包含关系单调
  • 如果w满足四边形不等式且关于区间包含关系单调,则f也满足四边形不等式
  • 如果f满足四边形不等式,则决策点 p [ i ] [ j − 1 ] ≤ p [ i ] [ j ] ≤ p [ i + 1 ] [ j ] p[i][j-1]\leq p[i][j]\leq p[i+1][j] p[i][j1]p[i][j]p[i+1][j],此时若把p看作一个矩阵则p在每一行和每一列上上单调不降

此时k的枚举由 [ i ,   j ] [i,\ j] [i, j]变为了 [ p [ i ] [ j − 1 ] ,   p [ i + 1 ] [ j ] ] [p[i][j-1],\ p[i+1][j]] [p[i][j1], p[i+1][j]]。只要将p矩阵打印出来观察每行每列是否单调即可,如果单调说明方程可以四边形优化。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值