[NOI2009]詩人小G(dp + 決策單調性優化)
阿新 • • 發佈:2018-12-30
題意
有一個長度為 \(n\) 的序列 \(A\) 和常數 \(L, P\) ,你需要將它分成若干段,每 \(P\) 一段的代價為 \(| \sum ( A_i ) − L|^P\) ,求最小代價的劃分方案。
\(n \le 10^5 , 1 \le P \le 10\)
題解
考慮暴力 \(O(n^2)\) dp。
\[
dp_i = \min_{j = 0} ^ {i - 1} |sum_j - sum_i - L|^P + dp_j
\]
這個方程是具有決策單調性的。
決策單調性是指,對於任意 \(u < v < i < j\) ,若在 \(i\) 處決策 \(v\) 優於決策 \(u\)
,則在 \(j\) 處必有決策 \(v\) 優於決策 \(u\) 。
至於證明,你考慮那是個二次函式,一階導數單增等性質就可以了。
然後考慮用一個棧來維護每個決策會更新的區間就行了,新加入一個決策時要二分得到它的區間。
具體二分的時候就找到 \(f(mid, i) - f(mid, stk[top])\) 的零點就行了,之後的點肯定 \(i\) 更優。
然後每次轉移的話就二分找到當前這個點屬於的決策區間,注意邊界問題,然後每次要把棧頂所有當前不優於它的狀態都要彈掉。
所以最後複雜度就是 \(O(n \log n)\) 的。
總結
決策單調性優化都可以用棧維護轉移區間,然後每次二分找轉移點,以及求區間就行了。
程式碼
#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << (x) << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) using namespace std; typedef long double ld; typedef long long ll; template<typename T> inline bool chkmin(T &a, T b) {return b < a ? a = b, 1 : 0;} template<typename T> inline bool chkmax(T &a, T b) {return b > a ? a = b, 1 : 0;} inline int read() { int x(0), sgn(1); char ch(getchar()); for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1; for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48); return x * sgn; } void File() { #ifdef zjp_shadow freopen ("P1912.in", "r", stdin); freopen ("P1912.out", "w", stdout); #endif } const int N = 1e5 + 1e3; int n, Pow, L; int sum[N], pre[N], stk[N], seg[N]; int ans[N]; char str[N][50]; ld dp[N]; ld fpm(ld x, int power) { ld res = 1; for (; power; power >>= 1, x *= x) if (power & 1) res *= x; return res; } ld Calc(int S, int T) { return S >= T ? 1e18 + 1 : dp[S] + fpm(abs(sum[T] - sum[S] - L), Pow); } int main () { File(); int cases = read(); while (cases --) { n = read(); L = read(); Pow = read(); For (i, 1, n) { scanf ("%s", str[i]); sum[i] = sum[i - 1] + strlen(str[i]) + 1; } ++ L; int top = 1; stk[1] = seg[1] = dp[0] = 0; For (i, 1, n) { int pos = upper_bound(seg + 1, seg + top + 1, i) - seg - 1; dp[i] = Calc(pre[i] = stk[pos], i); for (; top && Calc(stk[top], seg[top]) > Calc(i, seg[top]); -- top); int l = seg[top], r = n, res = 0; while (l <= r) { int mid = (l + r) >> 1; if (Calc(stk[top], mid) <= Calc(i, mid)) res = mid, l = mid + 1; else r = mid - 1; } if (res < n) seg[++ top] = res + 1, stk[top] = i; } if (dp[n] > 1e18) puts("Too hard to arrange"); else { printf ("%lld\n", (ll) dp[n]); top = 0; for (int u = n; u; u = pre[u]) ans[++ top] = u; ans[top + 1] = 0; Fordown (i, top, 1) For(j, ans[i + 1] + 1, ans[i]) printf ("%s%c", str[j], j == jend ? '\n' : ' '); } puts("--------------------"); } return 0; }