Description
你有一个长为 N 的序列 x1,x2,…,xN,一开始全部为 0,你现在可以以任意顺序进行任意次以下两种操作:
- 选定整数 k(1≤k≤N) 与不下降非负序列 c1,c2,…,ck,对所有 1≤i≤k,令 xi 加上 ci。
- 选定整数 k(1≤k≤N) 与不上升非负序列 c1,c2,…,ck,对所有 1≤i≤k,令 xN−k+i 加上 ci。
问最少进行多少次操作使得最后对任意 i 有 xi=Ai。
1≤N≤2×105,1≤Ai≤109。
Solution
首先如果只能做 1 操作,答案就是极长不降段的个数。只有 2 操作答案就是极长不升段的个数。
这启发我们将每个 Ai 拆成 ai 和 bi,分别表示来自 1 操作和 2 操作的贡献,答案即为:∑i=1N+1([ai<ai−1]+[bi>bi−1])。
考虑 dp。
设 fi,j 表示考虑了前 i 个数,满足 ai=j,bi=Ai−j 的最小操作数。
直接转移是 O(nV) 的,但是注意到 fi,j 不升,且 fi,ai≥fi,0−2,所以用记录一下三种值对应的区间即可。
时间复杂度:O(n)/O(nlogV)。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| #include <bits/stdc++.h>
const int kMaxN = 2e5 + 5;
int n; int a[kMaxN];
void dickdreamer() { std::cin >> n; for (int i = 1; i <= n; ++i) std::cin >> a[i]; int val = 0, f0 = 0, f1 = 1, f2 = 1; std::function<int(int)> getreal = [&] (int x) -> int { if (x >= f2) return 1e9; if (x <= f0) return val; else if (x <= f1) return val - 1; else return val - 2; }; for (int i = 1; i <= n + 1; ++i) { int _val, _f0, _f1; std::function<int(int, int)> getcoef = [&] (int v1, int v2) -> int { return (v1 > v2) + (a[i - 1] - v1 < a[i] - v2); }; std::function<int(int)> calc = [&] (int x) -> int { return std::min({getreal(0) + getcoef(0, x), getreal(f0 + 1) + getcoef(f0 + 1, x), getreal(f1 + 1) + getcoef(f1 + 1, x)}); }; _val = calc(0), _f0 = 0; int L = 0, R = a[i] + 1; while (L + 1 < R) { int mid = (L + R) >> 1; if (calc(mid) >= _val) L = _f0 = mid; else R = mid; } L = _f1 = _f0, R = a[i] + 1; while (L + 1 < R) { int mid = (L + R) >> 1; if (calc(mid) >= _val - 1) L = _f1 = mid; else R = mid; } val = _val, f0 = _f0, f1 = _f1, f2 = a[i] + 1; } std::cout << val << '\n'; }
int32_t main() { #ifdef ORZXKR freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0); int T = 1; while (T--) dickdreamer(); return 0; }
|