最大K段和
阿新 • • 發佈:2020-10-22
題目大意
有一個長度為 \(N\) 的序列 \(A\) 。他希望從中選出不超過 \(K\) 個連續子段,滿足它們兩兩不相交,求總和的最大值(可以一段也不選,答案為 \(0\))。
分析
很容易想到 \(O(n^2)\) 的 \(dp\)
設 \(f[i][j]\) 表示選到第 \(i\) 位,已選了 \(j\) 段時的最大答案
那麼 \(f[i][j] = \max(f[i-1][j] , s[i] + \max_\limits{0<l<i}(f[l][j-1] - s[l]))\)
然後維護最大的 \(f[l][j-1]-s[l]\) ,\(O(1)\) 更新即可
然後我們可以想到 \(WQS\)
它大概就是解決:有 \(n\) 個帶權物品,用滿足一定限制的方法選 \(m\) 個,使得其權值和取最值,而且權值和的最值是關於 \(m\) 的凸函式
注意 \(x\) 是段數
用直線 \(y=kx + b\) 去切
因為我們要求最大值,所以要最大化 \(b\)
\(b=y-kx\)
那麼我們就可以將原來的 \(dp\) 是改為
\(f[i]=\max(f[i-1] , s[i] - k + \max_\limits{0<l<i}(f[l]-s[l]))\)
總的來說,先二分 \(k\),然後判斷就 \(dp\),並記錄所分的段數
段數恰為 \(m\) 時就為答案
注意最後要以 \(k=ans\)
\(Code\)
#include<cstdio> #include<iostream> #include<cmath> using namespace std; typedef long long LL; const int N = 1e5 + 5; int n , k , a[N]; LL f[N] , g[N] , s[N] , l , r , mid , ans; bool check() { int x = 0; for(register int i = 1; i <= n; i++) { f[i] = f[i - 1] , g[i] = g[i - 1]; if (f[x] + s[i] - s[x] - mid > f[i]) f[i] = f[x] + s[i] - s[x] - mid , g[i] = g[x] + 1; if (f[i] - s[i] > f[x] - s[x]) x = i; } return g[n] >= k; } int main() { freopen("maxksum.in" , "r" , stdin); freopen("maxksum.out" , "w" , stdout); scanf("%d%d" , &n , &k); for(register int i = 1; i <= n; i++) scanf("%d" , &a[i]) , s[i] = s[i - 1] + a[i]; r = 0x3f3f3f3f3f3f3f3f; while (l <= r) { mid = (l + r) >> 1; if (check()) ans = mid , l = mid + 1; else r = mid - 1; } mid = ans , check(); printf("%lld" , f[n] + ans * k); }