# Topics Covered:

• 單調隊列優化
• 斜率優化
• Slope Trick
• Permutation Trick*
• Aliens Trick

*Permutation Trick 是我自己命名的w

# 單調隊列優化

aka Sliding Window

# "一路領先" 性質

• 加入一條直線(或任何有優超性的函數)
• 詢問某個位置在所有函數下的最大值

\forall x > p, f(x) \geq g(x) \\ \forall x \leq p, f(x) \leq g(x)

# 直接來Commando 吧

### 給你一個正整數序列與一個開口向下的二次函數$$f$$，你要把序列分成一些連續區間$$l_1-r_1, ..., l_k-r_k$$，使得

最大
\sum_{i = 1}^{k} f(\sum_{j = l}^{r} a[j])

# 變成前綴和展開

dp[i] = max_{j < i} (dp[j] + f(p_i - p_j))
dp[i] = max_{j < i} (dp[j] + a * p_i^2 - 2 * a * p_i * p_j + \\ a * p_j + b * p_i - b * p_j + c)
dp[i] = a * p_i^2 + b * p_i + max_{j < i} (- 2 * a * p_i * p_j + \\ a * p_j - b * p_j + c + dp[j])

# 變成前綴和展開

dp[i] = a * p_i^2 + b * p_i + max_{j < i} (- 2 * a * p_i * p_j + \\ a * p_j - b * p_j + c + dp[j])

#include <iostream>
#include <algorithm>
#include <vector>
#include <deque>
#include <utility>
#define ll long long
#define pii pair<ll, ll>
#define ff first
#define ss second
using namespace std;
ll ax, bx, cx;
inline ll f(ll x) {
return ax * x * x + bx * x + cx;
}
inline ll val(pii a, ll x) {
return a.first * x + a.second;
}
inline bool comp(pii a, pii b, ll x) {
return val(a, x) <= val(b, x);
}
int main() {
int t;
cin >> t;
while (t--) {
ll n;
cin >>n;
cin >> ax >> bx >> cx;
ll arr[n], pref[n];
for (int i = 0;i < n;i++) {
cin >> arr[i];
pref[i] = arr[i] + (i ? pref[i - 1] : 0);
}
ll dp[n];
deque<pii > deq;
deq.push_back(make_pair(0, cx));
for (int i = 0;i < n;i++) {
while (deq.size() >= 2) {
pii cur = deq.front();
deq.pop_front();
pii rep = deq.front();
//cout << val(cur, pref[i]) << "  " << val(rep, pref[i]) << endl;
if (!comp(cur, rep, pref[i])) {
deq.push_front(cur);
break;
}
}
/*
if (deq.size()) {
cout << deq.front().first << " " << deq.front().second <<endl;
}
*/
dp[i] = ax * pref[i] * pref[i] + bx * pref[i] + (deq.size() ? val(deq.front(), pref[i]) : 0);
//newline with -2a*pref[i], ax * pref[i] * pref[i] - b * pref[i] + pref[i] + dp[i])
pii add = make_pair(-2 * ax * pref[i], ax * pref[i] * pref[i] - bx * pref[i] + cx + dp[i]);
while (deq.size() >= 2) {
pii cur = deq.back();
deq.pop_back();
pii comp = deq.back();
if ((add.ss - comp.ss) * (comp.ff - cur.ff) <= (cur.ss - comp.ss) * (comp.ff - add.ff)) {
continue;
} else {
deq.push_back(cur);
break;
}
}
}
cout << dp[n - 1] << endl;
}
}

ORZCK

# Slope Trick

$$n \leq 3000, a_i \leq 10^9$$

$$O(n^2)$$? How about $$n \leq 10^5$$

## 糟糕作法

$$f(i, j) = min(f(i-1, j) + |a_i - j|, f(i, j-1))$$

ZCK: 你有聽過滾動DP嗎

a_1
a_1
a_2
a_2
a_1
a_1
a_2
a_3
a_2
a_3

# 實作:

//default code
int a[maxn];
int main() {
io
int n;
cin >> n;
priority_queue<int, vector<int>, less<int> > pq;
ll ans = 0;
for (int i = 0;i < n;i++) cin >> a[i], a[i] -= i;
for (int i = 0;i < n;i++) {
pq.push(a[i]);
if (pq.top() > a[i]) {
pq.push(a[i]);
ans += pq.top() - a[i];
pq.pop();
}
//debug(pq.top());
}
cout << ans << endl;
}

# Permutation Trick

## 酷酷題: JOIOC Skycraper

$$n \leq 100, a_i, L \leq 1000$$

## 由高到低放入!

9 \ \ \ 5 \ \ \ 8

AKA 講師也不會的東西

UPD: 高三還是不太會

# 換個例題好了

YTP 2021 程式挑戰賽 p6:

# 觀察函數圖形

$$f(k+1) - f(k)$$遞減。

Credit: Wiwiho

# 凸性的證明

$$k=1$$時，一定是選最大值。

2 4 3 5 -2

2 4 (3 + -2 - 5)

(2 + (-4) - 4)

# CSA Or Problem

## Credits/Other Resources

• ZCK DP 優化講義 (前面放的)
• Wiwiho 的簡報 (資讀社團內)
• USACO Guide

By justinlai2003

• 1,841