bzoj1049: [HAOI2006]數字序列 最長不降子序列的特殊操作
bzoj1049: [HAOI2006]數字序列
Description
現在我們有一個長度為n的整數序列A。但是它太不好看了,於是我們希望把它變成一個單調嚴格上升的序列。
但是不希望改變過多的數,也不希望改變的幅度太大。
Input
第一行包含一個數n,接下來n個整數按順序描述每一項的鍵值。n<=35000,保證所有數列是隨機的
Output
第一行一個整數表示最少需要改變多少個數。 第二行一個整數,表示在改變的數最少的情況下,每個數改變
的絕對值之和的最小值。
Sample Input
4
5 2 3 5
Sample Output
1
4
分析
第一問補集轉化一下,變成求
的一個序列。
令
變成求
的最長不降子序列。
對於第二問有一個神仙操作。
首先求出合法的轉移,一種辦法是把
掛到
上面,那麼對於一個
,所有合法的最優轉移一定是
中比
小的且
的值小於
的。
這樣的做法是
的,但是隨機情況下實際上答案很少。
考慮一組合法的轉移。如果是從
的,那麼這之間的數應該怎麼改變?
有一個簡單的想法就是肯定是把在
到
之間的數都挪到一條水平線上。
由於是一組合法的轉移,所以一定不存在在
和
之間的數。
所以對於比
小的數,肯定最少要把它們挪到
上,對於比
大的數,至少要把他們挪到
上。
可是有可能出現下面這種情況。
這個時候發現,對於不合法的那兩個節點,只能挪到一邊去。
至於挪到哪邊,或者是挪到某個中間點,代價是不變的。
因為一個少往上挪多少,另一個就要多往下挪多少,最後挪到一個相等的水平線上。
所以可以列舉一個分界點
,
之前的放在
上,
之後的放在
上的即可。
這個過程實際上是
的,但是在資料隨機意義下常數很小,可以通過。
程式碼
#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;
}