[HAOI2006]數字序列 題解
阿新 • • 發佈:2020-12-14
Solution
題目描述
現在我們有一個長度為 n的整數序列 a。但是它太不好看了,於是我們希望把它變成一個單調嚴格上升的序列。但是不希望改變過多的數,也不希望改變的幅度太大。
輸入格式
第一行是一個整數,表示序列長度 n。
第二行有 n個整數,第 i個整數表示序列的第 i項 ai。
輸出格式
第一行輸出一個整數,表示最少需要改變多少個數。
第二行輸出一個整數,表示在改變的數最少的情況下,每個數改變的絕對值之和的最小值。
輸入輸出樣例
輸入: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; }