动态规划优化 习题总结 1
动态规划优化 习题总结
《算法竞赛进阶指南》中的例题与练习题。
*A. P1081 [NOIP2012 提高组] 开车旅行(倍增优化 DP)
洛谷 P1081 | AcWing 293 | 码创未来
题意
小 A 和小 B 开车去东西排列的连续
旅行过程中,小 A 和小 B 轮流开车,第一天小 A 开车,之后每天轮换一次。他们计划选择一个城市
小 A 和小 B 的驾驶风格不同,小 B 总是沿着前进方向选择一个最近的城市作为目的地,而小 A 总是沿着前进方向选择第二近的城市作为目的地(注意:本题中如果当前城市到两个城市的距离相同,则认为离海拔低的那个城市更近)。如果其中任何一人无法按照自己的原则选择目的城市,或者到达目的地会使行驶的总距离超出
在启程之前,小 A 想知道两个问题:
-
对于一个给定的
,从哪一个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值最小(如果小 B 的行驶路程为 ,此时的比值可视为无穷大,且两个无穷大视为相等)。如果从多个城市出发,小 A 开车行驶的路程总数与小 B 行驶的路程总数的比值都最小,则输出海拔最高的那个城市。 -
对任意给定的
和出发城市 ,小 A 开车行驶的路程总数以及小 B 行驶的路程总数。
对于
数据保证
思路
本题有三个关键信息:所在城市、行驶天数、A 和 B 行驶的总距离。
由出发城市和天数我们可以知道现在所在的城市,以及当前谁在开车,并且可以算出两人分别和总共行驶的距离。
天数是一个连续的阶段,我们可以用倍增来优化。还需要设一维状态表示谁先开车,因为中间过程中不一定是 A 先开车。
-
设
表示从城市 出发,一共行驶了 天,出发时 先开车,最终能到达的城市,其中 , 表示 A 先开车, 表示 B 先开车。对于初值
,我们可以暴力处理出来。设
和 分别表示 A 和 B 从 出发到达的下一个城市, ,那么 即是令 取得最小值的 , 即是令 取得次小值的的 。(注意 A 对应次小值,B 对应最小值。。。)我们从后往前遍历城市
,将 插入一个std::multiset
即平衡树,这样已经在平衡树中的城市范围为 ,那么从当前城市 向前遍历两个、向后遍历两个城市并比较距离即可得出最近城市和第二近城市。具体实现可以参考代码。于是
。根据倍增的思想,并且特殊考虑 的情况(一共 天,第一天 开车,第二天 开车),得出状态转移方程。 -
接下来处理 A 和 B 行驶的距离。我们设
和 分别表示从城市 出发,一共行驶了 天,出发时 先开车,A 和 B 行驶的总距离。初值
; 。同样根据倍增思想,利用之前处理出的
数组,写出状态转移方程。 -
预处理出天数的二进制每一位的状态之后,我们来考虑一般性的问题。
设
表示从城市 出发,最多行驶 公里,A 和 B 行驶的路程。显然这个函数是一个二元组。根据二进制拆分的思想,我们递减枚举
的 次幂,累加到行驶的天数里。具体来说,我们设所在城市为
,A 和 B 行驶的路程分别为 和 。开始时 。然后倒序循环枚举 的幂次 , 。如果 ,说明可以继续行驶 天,于是我们更新数值,令 。循环结束后,所得的
即为答案。 -
有了
函数,我们就可以解决题目中提出的问题了。- 枚举
,打擂台求出 ; - 多次询问
。
- 枚举
代码
点击查看代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <set>
#include <algorithm>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define g(x, y, z) for (int x = (y); (x) >= (z); --(x))
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f3f3f3f3f;
int n, m, l, ga, gb, f[20][N][2], da[20][N][2], db[20][N][2];
struct City {
int id, h;
City() {}
City(int _id, int _h): id(_id), h(_h) {}
bool operator<(City const &o) const {
return h < o.h;
}
} a[N], tmp[4];
multiset<City> myset;
inline bool cmp(City const &p, City const &q) {
return p.h == q.h ? a[p.id].h < a[q.id].h : p.h < q.h;
}
pii calc(int s, int x) {
int p = s;
int la = 0, lb = 0;
g(i, l, 0) {
if (f[i][p][0] && la + lb + da[i][p][0] + db[i][p][0] <= x) {
la += da[i][p][0];
lb += db[i][p][0];
p = f[i][p][0];
}
}
return (pii){la, lb};
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
l = log2(n);
f(i, 1, n) cin >> a[i].h, a[i].id = i;
myset.insert(City(0, INF)), myset.insert(City(0, INF));
myset.insert(City(n + 1, -INF)), myset.insert(City(n + 1, -INF));
g(i, n, 1) {
auto it = myset.insert(a[i]); //insert()函数返回值正是刚刚插入的值的迭代器(这样就不用find()函数了)
auto it1 = ++it, it2 = ++it, it3 = --(--(--it)), it4 = --it; //移动std::multiset<>::iterator只能用++和--
tmp[0] = City((*it1).id, (*it1).h - a[i].h), tmp[1] = City((*it2).id, (*it2).h - a[i].h); //*iterator表示引用迭代器所
tmp[2] = City((*it3).id, a[i].h - (*it3).h), tmp[3] = City((*it4).id, a[i].h - (*it4).h); //指向的元素, 也可以用it->h
sort(tmp, tmp + 4, cmp);
gb = tmp[0].id, ga = tmp[1].id;
f[0][i][0] = ga, f[0][i][1] = gb;
da[0][i][0] = abs(a[i].h - a[ga].h), db[0][i][1] = abs(a[i].h - a[gb].h);
}
f(j, 1, n) f(k, 0, 1) { //i=1
f[1][j][k] = f[0][f[0][j][k]][k^1];
da[1][j][k] = da[0][j][k] + da[0][f[0][j][k]][k^1];
db[1][j][k] = db[0][j][k] + db[0][f[0][j][k]][k^1];
}
f(i, 2, l) f(j, 1, n) f(k, 0, 1) { //i>1
f[i][j][k] = f[i-1][f[i-1][j][k]][k];
da[i][j][k] = da[i-1][j][k] + da[i-1][f[i-1][j][k]][k];
db[i][j][k] = db[i-1][j][k] + db[i-1][f[i-1][j][k]][k];
}
int s = 0, x;
cin >> x;
double minn = INF;
f(i, 1, n) {
pii tmp = calc(i, x);
double sum = tmp.second == 0 ? INF : ((double)tmp.first / (double)tmp.second);
if (sum < minn) minn = sum, s = i;
else if (sum == minn && a[i].h > a[s].h) s = i;
}
cout << s << '\n';
cin >> m;
f(i, 1, m) {
cin >> s >> x;
pii tmp = calc(s, x);
cout << tmp.first << ' ' << tmp.second << '\n';
}
return 0;
}
*B. Count The Repetitions(倍增优化 DP)
题意
定义
称字符串
例如
给定两个字符串
(注意先输入
思路
首先发现
所以我们求的其实是最大的
注意到
令
最后统计答案的时候,用尽量大的
考虑每一个
由于所用的
即用
在
考虑倍增的思想,我们先用尽量短的
初值
接下来对 DP 的阶段
第一步,枚举起始位置
第二步,用
第三步,打擂台更新答案,枚举下一个
最后别忘了将答案除以
代码
点击查看代码
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
const int N = 110;
int n1, n2, l1, l2, l;
long long f[N][33], ans, tmp, x;
string s1, s2;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
while (cin >> s2 >> n2 >> s1 >> n1) {
l1 = s1.length(), l2 = s2.length();
l = log2(l1 * n1 / l2);
for (int i = 0; i < l1; ++i) {
int p = i;
f[i][0] = 0;
for (int j = 0; j < l2; ++j) { //匹配s2的每一位
int cnt = 0;
while (s1[p] != s2[j]) {
if (++p == l1) p = 0;
if (++cnt >= l1) { //s1中匹配不上这一位
cout << 0 << '\n';
goto label; //goto语句, 直接跳到循环最后的continue
}
}
if (++p == l1) p = 0;
f[i][0] += cnt + 1;
}
}
for (int j = 1; j <= l; ++j)
for (int i = 0; i < l1; ++i)
f[i][j] = f[i][j - 1] + f[(i + f[i][j - 1]) % l1][j - 1];
x = tmp = 0;
for (int j = l; j >= 0; --j)
if (x + f[x % l1][j] <= l1 * n1)
x += f[x % l1][j], tmp += (1 << j);
cout << tmp / n2 << '\n';
label:
continue;
}
return 0;
}
C. [USACO04DEC]Cleaning Shifts S(线段树优化 DP,离散化)
POJ 2376 | AcWing 295 | 码创未来
题意
给定
思路
设
将区间按
对于当前区间
对于取 min 的操作,我们用线段树来维护。数据范围较大,需要离散化(或许也可以不离散化)。
代码
点击查看代码
#include <iostream>
#include <cstring>
#include <algorithm>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
using namespace std;
const int N = 2.5e4 + 10;
const int INF = 0x3f3f3f3f;
int n, m, f[N << 2], ans = INF, raw[N << 2], cnt;
struct Node {
int l, r;
friend bool operator<(Node const &p, Node const &q) {
return p.r < q.r;
}
} a[N];
struct SegTree {
#define lson (u << 1)
#define rson (u << 1 | 1)
struct Node {
int l, r, minn;
} tr[N << 4];
inline void pushup(int u) { tr[u].minn = min(tr[lson].minn, tr[rson].minn); }
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
tr[u].minn = l ? INF : 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
// pushup(u);
}
void modify(int u, int x, int v) {
if (tr[u].l == x && tr[u].r == x) {
tr[u].minn = v;
return;
}
int mid = (tr[u].l + tr[u].r) >> 1;
if (x <= mid) modify(lson, x, v);
else modify(rson, x, v);
pushup(u);
return;
}
int query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) return tr[u].minn;
int res = INF;
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) res = min(res, query(lson, l, r));
if (r > mid) res = min(res, query(rson, l, r));
return res;
}
} t;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
raw[++cnt] = 1, raw[++cnt] = m;
f(i, 1, n) {
cin >> a[i].l >> a[i].r;
raw[++cnt] = a[i].l, raw[++cnt] = a[i].r;
raw[++cnt] = a[i].l + 1, raw[++cnt] = a[i].r + 1;
}
sort(a + 1, a + n + 1);
sort(raw + 1, raw + cnt + 1);
cnt = unique(raw + 1, raw + cnt + 1) - raw - 1;
while (raw[cnt] > m) --cnt;
memset(f, 0x3f, sizeof f);
f[0] = 0;
t.build(1, 0, cnt);
f(i, 1, n) {
a[i].l = lower_bound(raw + 1, raw + cnt + 1, a[i].l) - raw;
a[i].r = lower_bound(raw + 1, raw + cnt + 1, a[i].r) - raw;
int tmp = t.query(1, a[i].l - 1, a[i].r - 1) + 1;
if (f[a[i].r] > tmp) {
f[a[i].r] = tmp;
t.modify(1, a[i].r, f[a[i].r]);
}
}
if (f[cnt] == INF) cout << "-1\n";
else cout << f[cnt] << '\n';
return 0;
}
D. [USACO05DEC]Cleaning Shifts S(线段树优化 DP)
洛谷 P4644 | POJ 3171 | AcWing 296 | 码创未来
题意
给定
思路
与上一题不同之处在于不需要离散化;每个区间有一定的代价;覆盖的范围为
令
用线段树维护区间最小值。答案为
代码
点击查看代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define il inline
using namespace std;
const int N = 1e4 + 10, M = 1e5 + 10;
int n, L, R;
long long f[M];
struct Cow {
int a, b, c;
bool operator<(Cow const &o) const {
return b < o.b;
}
} a[N];
namespace SegTree {
#define lson (u << 1)
#define rson (u << 1 | 1)
struct Node {
int l, r;
long long x;
} tr[M << 2];
il void pushup(int u) { tr[u].x = min(tr[lson].x, tr[rson].x); }
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
if (l == r) return tr[u].x = f[l], void();
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
pushup(u);
return;
}
void modify(int u, int x, long long y) {
if (tr[u].l == x && tr[u].r == x) {
tr[u].x = y;
return;
}
// pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (x <= mid) modify(lson, x, y);
else modify(rson, x, y);
pushup(u);
return;
}
long long query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].x;
}
long long res = 4557430888798830399LL; //0x3f3f3f3f3f3f3f3f
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) res = min(res, query(lson, l, r));
if (r > mid) res = min(res, query(rson, l, r));
return res;
}
}
using SegTree::build;
using SegTree::modify;
using SegTree::query;
signed main() {
scanf("%d%d%d", &n, &L, &R);
++L, ++R;
memset(f, 0x3f, sizeof f);
f[L - 1] = 0;
f(i, 1, n) {
scanf("%lld%lld%lld", &a[i].a, &a[i].b, &a[i].c);
a[i].a = max(++a[i].a, L);
a[i].b = min(++a[i].b, R);
}
sort(a + 1, a + n + 1);
build(1, L - 1, R);
f(i, 1, n) {
f[a[i].b] = min(f[a[i].b], query(1, a[i].a - 1, a[i].b - 1) + a[i].c);
modify(1, a[i].b, f[a[i].b]);
}
if (f[R] >= 4557430888798830399LL) puts("-1");
else printf("%lld\n", f[R]);
return 0;
}
*E. The Battle of Chibi(树状数组优化 DP,离散化)
洛谷 UVA12983 | AcWing 297 | 码创未来
题意
给定长度为
对于每组测试数据,
对于每个测试点,数据满足
思路
设
可以写出状态转移方程:
如果每次都要重新扫一遍 喜提一个大大的 TLE。如何优化呢?
我们先把外层循环的
由于
由于
当枚举到每一个
然后枚举
- 查询小于
的所有 的 的和,修改 的值; - 向树状数组中
对应的位置 上插入 。
答案为
代码
点击查看代码
#include <iostream>
#include <cstring>
#include <unordered_map>
#include <algorithm>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define il inline
using namespace std;
const int N = 1e3 + 10;
const int MOD = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int tt, n, m, a[N], f[N][N], raw[N], c[N], cnt, ans;
unordered_map<int, int> val;
il int lowbit(int x) { return x & (-x); }
void add(int x, int y) {
while (x <= cnt) {
c[x] += y;
c[x] %= MOD;
x += lowbit(x);
}
return;
}
int query(int x) {
int res = 0;
while (x >= 1) {
res += c[x];
res %= MOD;
x -= lowbit(x);
}
return res;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> tt;
f(x, 1, tt) {
val.clear();
ans = 0;
cin >> n >> m;
f(i, 1, n) cin >> a[i], raw[i] = a[i];
raw[0] = -INF;
sort(raw, raw + n + 1);
cnt = unique(raw, raw + n + 1) - raw;
f(i, 0, cnt - 1) val[raw[i]] = i + 1;
// f[0][0] = 1;
f(i, 1, m) {
memset(c, 0, sizeof c);
add(1, (bool)(i == 1));
f(j, 1, n) {
f[i][j] = query(val[a[j]] - 1);
add(val[a[j]], f[i - 1][j]);
}
}
f(i, m, n) ans += f[m][i], ans %= MOD;
cout << "Case #" << x << ": " << ans << '\n';
}
return 0;
}
*F. Cut The Sequence(单调队列优化 DP)
POJ 3017 | AcWing 299 | 码创未来
TO DO...
G. Bribing FIPA(树上背包 DP)
洛谷 UVA1222 | AcWing 324 | 码创未来
题意
给定一个
要求选定若干节点,使得价值总和
求选定节点的费用和的最小值。
思路
选定一个节点,就相当于选定了它下面的所有点。
设
令
初值
由于一个
为了把森林变成一棵树,我们建立一个超级根节点
代码
点击查看代码
#include <cstdio>
#include <sstream>
#include <cstring>
#include <unordered_map>
#include <string>
#include <vector>
#include <bitset>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define g(x, y, z) for (int x = (y); (x) >= (z); --(x))
using namespace std;
const int N = 210;
const int INF = 0x3f3f3f3f;
int n, m, c, a[N], siz[N], f[N][N], ans;
char s[22000];
unordered_map<string, int> mp;
vector<int> e[N];
bitset<N> isRoot;
int dfs(int u) {
memset(f[u], 0x3f, sizeof f[u]);
f[u][0] = 0;
int siz = 1;
for (auto v: e[u]) {
siz += dfs(v);
g(i, n, 0) f(j, 0, i)
f[u][i] = min(f[u][i], f[u][j] + f[v][i - j]);
}
f[u][siz] = min(f[u][siz], a[u]);
return siz;
}
signed main() {
while (true) {
fgets(s, sizeof s, stdin); //读入整行存到字符串中
if (s[0] == '#') break;
sscanf(s, "%d%d", &n, &m); //从字符串中读入数据
c = 0;
mp.clear();
f(i, 0, n) e[i].clear();
isRoot.set(); //全部设为1
f(i, 1, n) {
int x, y, tmp;
scanf("%s %d", s, &tmp);
if (mp.find(s) == mp.end()) mp[s] = ++c;
x = mp[s];
a[x] = tmp;
fgets(s, sizeof s, stdin); //读入整行
stringstream ss(s); //字符串流
string str;
while (ss >> str) { //在字符串流中读入字符串
if (mp.find(str) == mp.end()) mp[str] = ++c;
y = mp[str];
isRoot[y] = 0;
e[x].push_back(y); //建边(指向儿子)
}
}
f(i, 1, n) if (isRoot[i]) e[0].push_back(i); //超级根节点
a[0] = INF;
dfs(0);
ans = INF;
f(i, m, n) ans = min(ans, f[0][i]);
printf("%d\n", ans);
}
return 0;
}
H. Computer(换根树上 DP)
题意
给定一棵
思路
(简单题,说得挺多,实际上没什么。。)
对于一个节点,这条最长的路径可能是向下到子树中的,也可能是向上经过父亲的。
我们设节点
那么节点
对于
对于
但是!!!我们发现一个重要的 bug:如果长度为
所以我们再保存一个由节点向下到叶子的所有路径的次大值,并且保存最大值是从哪个儿子更新来的。
设
处理
答案为
代码
点击查看代码
#include <iostream>
#include <cstring>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
using namespace std;
const int N = 1e5 + 10;
int n, dlf[N][2], son[N], dup[N];
struct Edge {
int to, nxt, val;
} e[N << 1];
int head[N], cnt;
inline void add(int from, int to, int val) {
e[++cnt].to = to, e[cnt].nxt = head[from], e[cnt].val = val, head[from] = cnt;
return;
}
void dfs1(int u, int fa) {
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to, w = e[i].val;
if (v == fa) continue;
dfs1(v, u);
if (dlf[v][0] + w > dlf[u][0]) {
dlf[u][1] = dlf[u][0];
dlf[u][0] = dlf[v][0] + w, son[u] = v;
} else if (dlf[v][0] + w > dlf[u][1]) {
dlf[u][1] = dlf[v][0] + w;
}
}
}
void dfs2(int u, int fa) {
for (int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to, w = e[i].val;
if (v == fa) continue;
dup[v] = max(dup[u], (son[u] == v) ? dlf[u][1] : dlf[u][0]) + w;
dfs2(v, u);
}
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
while (cin >> n) {
memset(dlf, 0, sizeof dlf);
memset(dup, 0, sizeof dup);
memset(head, 0, sizeof head);
cnt = 0;
f(i, 2, n) {
int y, l;
cin >> y >> l;
add(i, y, l), add(y, i, l);
}
dfs1(1, 1);
dfs2(1, -1);
f(i, 1, n) cout << max(dlf[i][0], dup[i]) << '\n';
}
return 0;
}
*I. [HNOI2011]XOR和路径(有后效性 DP,高斯消元,期望)
洛谷 P3211 | AcWing 326 | 码创未来
TO DO...
J. Fence Obstacle Course(线段树优化 DP)
POJ 2374 | AcWing 329 | 码创未来
题意
农夫约翰为他的奶牛们建造了一个围栏障碍训练场,以供奶牛们玩耍。
训练场由
训练场的出口位于原点
这些牛会从起点处开始向下走,当它们碰到围栏时会选择沿着围栏向左或向右走,走到围栏端点时继续往下走,按照此种走法一直走到出口为止。
求出这些牛从开始到结束,行走的水平距离的最小值。
思路
为了方便转移,我们将从上往下走改为从下往上走,从
设
初值:
设
那么状态转移方程为:
答案为
还有一个问题:如何快速求出
我们需要一个支持区间覆盖、单点查询的数据结构,线段树标记下传可以很好地解决。
代码
点击查看代码
#include <iostream>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
using namespace std;
const int N = 5e4 + 10, M = 3e5 + 10;
int n, s, f[N][2], a[N], b[N];
inline int abs(int x) { return __builtin_abs(x); }
struct SegTree { //区间覆盖, 单点查询
#define lson (u << 1)
#define rson (u << 1 | 1)
struct Node {
int l, r, x;
} tr[M << 2];
inline void pushdown(int u) {
if (~tr[u].x) tr[lson].x = tr[rson].x = tr[u].x, tr[u].x = -1;
return;
}
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
if (l == r) return;
else tr[u].x = -1;
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
return;
}
void modify(int u, int l, int r, int x) {
if (l <= tr[u].l && tr[u].r <= r) return tr[u].x = x, void();
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) modify(lson, l, r, x);
if (r > mid) modify(rson, l, r, x);
return;
}
int query(int u, int x) {
if (tr[u].l == x && tr[u].r == x) return tr[u].x;
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (x <= mid) return query(lson, x);
else return query(rson, x);
}
} t;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> s;
s += 1e5;
t.build(1, 0, 2e5);
// t.modify(1, 0, 2e5, 0);
a[0] = 1e5, b[0] = 1e5;
// f[0][0] = f[0][1] = 0;
f(i, 1, n) {
cin >> a[i] >> b[i];
a[i] += 1e5, b[i] += 1e5;
int j = t.query(1, a[i]);
f[i][0] = min(f[j][0] + abs(a[i] - a[j]), f[j][1] + abs(a[i] - b[j]));
j = t.query(1, b[i]);
f[i][1] = min(f[j][0] + abs(b[i] - a[j]), f[j][1] + abs(b[i] - b[j]));
t.modify(1, a[i], b[i], i);
}
cout << min(f[n][0] + abs(a[n] - s), f[n][1] + abs(b[n] - s)) << '\n';
return 0;
}
*K. [USACO09OPEN]Tower of Hay G(单调队列优化 DP)
洛谷 P4954 | AcWing 331 | 码创未来
题意
一共有
Bessie 必须利用所有
她必须按照从
Bessie 的目标是建立起最高的干草堆。求出最高的干草堆的高度。
思路
由于从下到上搭干草堆不好维护,我们考虑从上到下搭干草堆。那么应依次读入
猜想:如果使底层宽度最小,那么一定可以构造出一种层数最高的方案。
证明:对于当前的所有干草,任取一个能使层数最高的方案,设有
层,把其中从下往上每一层最大的块编号记为 ;任取一个能使底边最短的方案,设有 层,把其中从下往上每一层最大的块编号记为 。显然 ,这说明至少存在一个 ,满足 且 。也就是说,方案 第 层完全被方案 第 层包含。构造一个新方案,第 层往上按方案 ,往下按方案 ,两边都不要的块放中间当第 层。新方案的层数与 相同,而底边长度与 相同。证毕。(proof by zkw)(引自 USACO 2009 Open 干草塔Tower of Hay 解题报告 - Lazycal - 博客园)
设
设
状态转移方程:
含义是:枚举
设
总之这道题的特殊之处在于不是直接取队首,而是取最后一个满足条件的队首。
代码
点击查看代码
#include <iostream>
#define f(x, y, z) for (int x = (y); (x) <= (z); ++(x))
#define g(x, y, z) for (int x = (y); (x) >= (z); --(x))
using namespace std;
const int N = 1e5 + 10;
int n, w[N], s[N], q[N], h, t, f[N], g[N], ans;
signed main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
g(i, n, 1) cin >> w[i];
h = t = 1;
f[0] = 0;
f(i, 1, n) {
s[i] = s[i - 1] + w[i];
while (h <= t && g[q[h]] + s[q[h]] <= s[i]) ++h;
int j = q[h - 1];
f[i] = f[j] + 1;
ans = max(ans, f[i]);
g[i] = s[i] - s[j];
while (h <= t && g[q[t]] + s[q[t]] >= g[i] + s[i]) --t;
q[++t] = i;
}
cout << ans << '\n';
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】