1. 程式人生 > 實用技巧 >[HAOI2006]數字序列 題解

[HAOI2006]數字序列 題解

Solution

題目描述

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

輸入格式

第一行是一個整數,表示序列長度 n
第二行有 n個整數,第 i個整數表示序列的第 iai

輸出格式

第一行輸出一個整數,表示最少需要改變多少個數。

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

輸入輸出樣例

輸入:4 5 2 3 5

輸出:1 4


第一問

第一問題目非常easy

我們發現如果要同時保留a序列中a[i]與a[j]兩個元素,則必須要滿足 a[j]-a[i]≥j-i;

移項一下 我們發現 a[j]-j≥a[i]-i;

於是我們發現,我們可以開一個b陣列其中對於任意元素b[i]=a[i]-i;

顯然 這個b序列一定是不下降的;

於是最少改變多少個數,也就變成了要求b序列的最長不下降子序列,我們可以直接用dp來做複雜度 O(nlogn)

如果不明白我程式碼中寫的話 請看這裡

當然 也可以用樹狀陣列來完成這一操作接下來請看第二問

第二問

第二問 我們發現一種情況是我們要解決的

每個被保留的b[i]和b[j]之間的均不合法,那麼如何來改變b[i]與b[j]呢?

後面我來給個詳細的證明吧 如果結合我的程式碼思考不出來的話,請看這位大佬的部落格很詳細

就是不斷的縮減所謂的“臺階”,最後剩下左邊的高b[i]與右邊的高b[j]

最優解一定是(或者說,一定可以是)左邊的b[i]到b[k]全部變成b[i]並且右邊的b[k+1]到b[j]全部變成b[j]

如果最優解不是這樣,我們可以無償甚至減償來變成這種形態

然後列舉每個區間的k就好了

code:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register 
const int maxn=4e4+9,INF=0x3f3f3f3f;
int a[maxn],f[maxn],b[maxn],d[maxn],l[maxn],n,len=1,sum1[maxn],sum2[maxn];
vector
<int> p[maxn]; signed main() { scanf("%lld",&n); for(re int i=1;i<=n;i++) scanf("%lld",&a[i]),b[i]=a[i]-i; d[1]=b[1];b[n+1]=INF; l[1]=1; p[1].push_back(1); for(re int i=2;i<=n+1;i++) { if(d[len]<=b[i]) { d[++len]=b[i]; l[i]=len; p[len].push_back(i); } else { int pos=upper_bound(d+1,d+1+len,b[i])-d; d[pos]=b[i]; l[i]=pos; p[pos].push_back(i); } } printf("%lld\n",n-len+1); p[0].push_back(0); b[0]=-INF;b[n+1]=INF; memset(f,INF,sizeof f); f[0]=0; for(re int i=1;i<=n+1;i++) { for(re int h=0,size=p[l[i]-1].size();h<size;++h) { int j=p[l[i]-1][h]; if(j>i||b[j]>b[i]) continue; sum1[j]=0; for(re int k=j+1;k<=i-1;k++) sum1[k]=sum1[k-1]+abs(b[k]-b[j]); sum2[i-1]=0; for(re int k=i-2;k>=j;k--) sum2[k]=sum2[k+1]+abs(b[k+1]-b[i]); for(re int k=j;k<=i-1;k++) f[i]=min(f[i],f[j]+sum1[k]+sum2[k]); } } printf("%lld",f[n+1]); return 0; }