文章目录
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]][s−1−i]);
由于二叉树中节点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][j−1]≤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][j−1], p[i+1][j]]。只要将p矩阵打印出来观察每行每列是否单调即可,如果单调说明方程可以四边形优化。