1. 程式人生 > >@bzoj - [email protected] 檸檬

@bzoj - [email protected] 檸檬

目錄


@[email protected]

一共有 N 只貝殼,編號為 1...N,貝殼 i 的大小為 si。
Flute 每次可以取一段連續的貝殼,並選擇 s0。如果這些貝殼中大小為 s0 的貝殼有 t 只,就通過魔法把這些貝殼變成 s0*t^2 只檸檬。
經過任意次魔法取完貝殼,最終 Flute 得到的檸檬數是所有小段檸檬數的總和。問最多能變出多少檸檬。

input


第 1 行:一個整數,表示 N(1 ≤ N ≤ 100,000)。
第 2 .. N + 1 行:每行一個整數,第 i + 1 行表示 si(1 ≤ si ≤10,000)。

output
僅一個整數,表示 Flute 最多能得到的檸檬數。

sample input
5
2
2
5
2
3
sample output
21
sample explain
Flute 先從取下 4 只貝殼,它們的大小為 2, 2, 5, 2。選擇 s0 = 2,那麼這一段
裡有 3 只大小為 s0 的貝殼,通過魔法可以得到 2×3^2 = 18 只檸檬。再從右端取下最後一隻貝殼,通過魔法可以得到 1×3^1 = 3 只檸檬。總共可以得到 18 + 3 = 21 只檸檬。沒有比這更優的方案了。

@[email protected]

有這樣一個性質:假如你對於區間 [l, r] 選的貝殼大小為 s0,則貝殼 l 與 r 的大小為 s0。
如果區間的左右端點的大小不為 s0,則你可以往內縮端點直到等於為止,此時這個區間的答案不會變化。
這樣我們就可以來設計 dp 了。

定義狀態 \(dp[i]\) 表示取完 1~i 這些貝殼所能求得的最大檸檬數,再定義 \(f[i]\) 表示 i 前面的,與 i 大小相同的貝殼數量(類字首和)。
則有狀態轉移:
\[dp[i] = dp[j-1] + s[i]*(f[i]-f[j]+1)*(f[i]-f[j]+1) (s[i] = s[j]且j \leq i)\]


長得非常 “斜率優化”,我們拆開括號再仔細來看一看:
\[dp[i] = dp[j-1] + s[i]*(f[i]+1)^2-2*s[i]*f[i]*f[j]+s[i]*f[j]^2\]
注意 s[i] = s[j]。所以式子還可以變為:
\[dp[i] = (dp[j-1]+s[j]*f[j]^2) + (s[i]*(f[i]+1)^2)-2*s[i]*f[i]*f[j]\]
然後就可以斜率優化了。我們對於每一個 \(s[i]\) 分別進行斜率優化,斜率 \(2*s[i]*f[i]\) 是單增的,橫座標 \(f[j]\) 也是單增,求最大值即上凸包,對於每一個 \(s[i]\) 都開一個單調棧就 OK 了。

講一點程式碼細節:同時維護多個單調棧可以不用 vector 或者 deque 等動態的儲存,因為我們已知了所有不同型別的 si 對應的元素個數,所以它的單調棧長度必然不會超過這個值。
我們開一個總的長度為 N 的空間,然後按照元素個數分配棧頂的指標即可。

@accepted [email protected]

#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 100000;
const int MAXS = 10000;
ll dp[MAXN + 5], sum[MAXN + 5];
int nxt[MAXN + 5], adj[MAXN + 5], s[MAXN + 5];
ll c(int i) {return (sum[i] + 1)*(sum[i] + 1)*s[i];}
ll k(int i) {return 2LL*s[i]*(sum[i] + 1);}
ll x(int j) {return sum[j];}
ll y(int j) {return dp[j-1] + sum[j]*sum[j]*s[j];}
double slope(int p, int q) {return 1.0*(y(p) - y(q))/(x(p) - x(q));}
int stk[MAXN + 5], tp[MAXS + 5], cnt[MAXS + 5];
int main() {
    int N; scanf("%d", &N);
    for(int i=1;i<=N;i++) {
        scanf("%d", &s[i]);
        sum[i] = sum[adj[s[i]]] + 1;
        nxt[i] = adj[s[i]];
        adj[s[i]] = i;
        cnt[s[i]]++;
    }
    for(int i=1;i<=MAXS;i++)
        cnt[i] += cnt[i-1];
    for(int i=1;i<=MAXS;i++)
        tp[i] = cnt[i-1];
    for(int i=1;i<=N;i++) {
        while( tp[s[i]] > cnt[s[i]-1] + 1 && slope(stk[tp[s[i]]], i) >= slope(stk[tp[s[i]] - 1], stk[tp[s[i]]]) )
            tp[s[i]]--;
        stk[++tp[s[i]]] = i;
        while( tp[s[i]] > cnt[s[i]-1] + 1 && slope(stk[tp[s[i]] - 1], stk[tp[s[i]]]) <= k(i) )
            tp[s[i]]--;
        dp[i] = c(i) + y(stk[tp[s[i]]]) - k(i)*x(stk[tp[s[i]]]);
    }
    printf("%lld\n", dp[N]);
}

@[email protected]

一開始我想的是對於每一個 si 都從頭到尾作一遍 dp,這樣就只需要一個單調棧了。
後來發現,不同的 si 之間也會相互影響,所以我只能從前往後老老實實維護多個單調棧了……