1. 程式人生 > >bzoj1049: [HAOI2006]數字序列 最長不降子序列的特殊操作

bzoj1049: [HAOI2006]數字序列 最長不降子序列的特殊操作

bzoj1049: [HAOI2006]數字序列

Description

現在我們有一個長度為n的整數序列A。但是它太不好看了,於是我們希望把它變成一個單調嚴格上升的序列。
但是不希望改變過多的數,也不希望改變的幅度太大。

Input

第一行包含一個數n,接下來n個整數按順序描述每一項的鍵值。n<=35000,保證所有數列是隨機的

Output

第一行一個整數表示最少需要改變多少個數。 第二行一個整數,表示在改變的數最少的情況下,每個數改變
的絕對值之和的最小值。

Sample Input

4
5 2 3 5

Sample Output

1
4

分析

第一問補集轉化一下,變成求 a j &lt; a i , a

i a j i j a_j&lt;a_i,a_i-a_j\ge i-j
的一個序列。
b i = a i i b_i=a_i-i 變成求 b b 的最長不降子序列。
對於第二問有一個神仙操作。
首先求出合法的轉移,一種辦法是把 i i 掛到 f i f_i 上面,那麼對於一個 i i ,所有合法的最優轉移一定是 f i 1 f_i-1 中比 i i 小的且 b b 的值小於 b i b_i 的。
這樣的做法是 O ( n 2 ) O(n^2) 的,但是隨機情況下實際上答案很少。
考慮一組合法的轉移。如果是從 i &gt; j i-&gt;j 的,那麼這之間的數應該怎麼改變?
有一個簡單的想法就是肯定是把在 i i j j 之間的數都挪到一條水平線上。
在這裡插入圖片描述
由於是一組合法的轉移,所以一定不存在在 b i b_i b j b_j 之間的數。
所以對於比 i i 小的數,肯定最少要把它們挪到 i i 上,對於比 j j 大的數,至少要把他們挪到 j j 上。
可是有可能出現下面這種情況。
在這裡插入圖片描述
這個時候發現,對於不合法的那兩個節點,只能挪到一邊去。
至於挪到哪邊,或者是挪到某個中間點,代價是不變的。
因為一個少往上挪多少,另一個就要多往下挪多少,最後挪到一個相等的水平線上。
所以可以列舉一個分界點 k k k k 之前的放在 b i b_i 上, k k 之後的放在 b j b_j 上的即可。
這個過程實際上是 O ( n 3 ) O(n^3) 的,但是在資料隨機意義下常數很小,可以通過。

程式碼

#include<cstdio>
#include<cmath>
#include<algorithm>
const int N = 35500, tf = 0x3f3f3f3f; const long long lf = 0x3f3f3f3f3f3f3f3f;
int ri() {
    char c = getchar(); int x = 0, f = 1; for(;c < '0' || c > '9'; c = getchar()) if(c == '-') f  = -1;
    for(;c >= '0' && c <= '9'; c = getchar()) x = (x << 1) + (x << 3) - '0' + c; return x * f;
}
int a[N], b[N], f[N], pr[N], nx[N], to[N], l, C; long long g[N], s1[N], s2[N];
void add(int u, int v) {to[++C] = v; nx[C] = pr[u]; pr[u] = C;}
int F(int x) {
    int L = 1, R = l, m; 
    for(;L != R; a[b[m = L + R >> 1]] > x ? R = m : L = m + 1) ;
    return L;
}
int Cal(int L, int R, int x) {
    int r = 0;
    for(int i = L;i <= R; ++i) r += abs(a[i] - x);
    return r;
}
int main() {
    int n = ri();
    for(int i = 1;i <= n; ++i) a[i] = ri() - i;
    b[l = 1] = 1; f[1] = 1; a[++n] = tf;
    for(int i = 2;i <= n; ++i) {
        if(a[i] >= a[b[l]]) b[f[i] = ++l] = i;
        else b[f[i] = F(a[i])] = i;
    }
    printf("%d\n", n - l); 
    for(int i = n; ~i; --i) add(f[i], i), g[i] = lf;
    g[0] = 0; a[0] = -tf;
    for(int x = 1;x <= n; ++x) 
        for(int i = pr[f[x] - 1], v; i; i = nx[i]) {
            if((v = to[i]) > x) break;
            if(a[v] > a[x]) continue;
            for(int j = v; j <= x; ++j) s1[j] = abs(a[v] - a[j]), s2[j] = abs(a[x] - a[j]);
            for(int j = v + 1; j <= x; ++j) s1[j] += s1[j - 1], s2[j] += s2[j - 1];
            for(int j = v; j < x; ++j) g[x] = std::min(g[x], g[v] + s1[j] - s1[v] + s2[x] - s2[j]);
        }
    printf("%lld\n", g[n]);
    return 0;
}