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

[luogu4331]數字序列

令$a'_{i}=a_{i}+n-i$、$b'_{i}=b_{i}+n-i$,代價仍然是$\sum_{i=1}^{n}|a'_{i}-b'_{i}|$,但條件變為了$b'_{i}\le b'_{i+1}$,即不下降(以下為了方便,$a'_{i}$和$b'_{i}$仍然用$a_{i}$和$b_{i}$表示,原來的不需要考慮)

考慮暴力dp,令$f_{i,j}$表示前$i$個數且$b_{i}=j$的最小的代價,轉移時先令$f_{i-1,j}=\min_{k\le j}f_{i-1,k}$,再加上絕對值,即$f_{i,j}=f_{i-1,j}+|a_{i}-j|$

由於過程是交替進行的,可以看作先加上絕對值再取min(體現在定義上就是$f_{i,j}$表示前$i$個數且$b_{i}\le j$的最小的代價),細節上由於第1次全部都是0本身就不需要取min,然後最後答案即為$f_{n,\infty}$

歸納$f_{i}$具有以下性質:其斜率單調不遞增且小於等於0(即下凸但沒有上升的部分)

在這一條件下,對於取絕對值再取min的過程,可以看作:對於$a_{i}$左半部分相當於斜率增加1,對於$a_{i}$右半部分斜率減少1,特別的,由於要取min,因此對於斜率為0的部分仍然不變

(做法上有一點類似[AGC049E](https://www.cnblogs.com/PYWBKTDA/p/14015313.html))

定義$f'_{i,j}=f_{i,j}-f_{i,j-1}$($j\ge 1$),特別的$f'_{i,0}=\sum_{j=1}^{i}a_{i}$,那麼$f_{i,j}=\sum_{k=0}^{j}f'_{i,k}$(其實後面兩點不重要,因為求答案肯定通過構造出來的方案求更方便QAQ)

然後對於$f'_{i,j}$($1\le j\le a_{i}$)區間減1,對於$f'_{i,j}$($a_{i}<j$且$f'_{i,j}<0$)區間加1,直接用線段樹維護即可

關於最優解的構造:先有$b_{n}=\min_{f'_{n,k}=0}k$,然後再令$b_{i}=\min(b_{i+1},\min_{f'_{i,k}=0}k-1)$即可(很明顯$b_{i+1}$之前$f_{i,j}$最小的位置就是這裡,同時這裡的$f_{i,j}$不會是跟$f_{i,j-1}$取min的結果)

 1 #include<bits/stdc++.h>
 2 using namespace
std; 3 #define N 1000005 4 #define L (k<<1) 5 #define R (L+1) 6 #define mid (l+r>>1) 7 map<int,int>mat; 8 map<int,int>::iterator it; 9 int n,m,a[N],b[N],val[N],f[N<<2],tag[N<<2]; 10 long long ans; 11 char ch[21]; 12 int read(){ 13 int x=0; 14 char c=getchar(); 15 while ((c<'0')||(c>'9'))c=getchar(); 16 while (('0'<=c)&&(c<='9')){ 17 x=x*10+c-'0'; 18 c=getchar(); 19 } 20 return x; 21 } 22 void write(int k){ 23 int s=0; 24 while (k){ 25 ch[++s]=k%10+'0'; 26 k/=10; 27 } 28 while (s)putchar(ch[s--]); 29 putchar(' '); 30 } 31 void upd(int k,int x){ 32 tag[k]+=x; 33 f[k]+=x; 34 } 35 void down(int k){ 36 upd(L,tag[k]); 37 upd(R,tag[k]); 38 tag[k]=0; 39 } 40 void update(int k,int l,int r,int x,int y,int z){ 41 if ((l>y)||(x>r))return; 42 if ((x<=l)&&(r<=y)){ 43 upd(k,z); 44 return; 45 } 46 down(k); 47 update(L,l,mid,x,y,z); 48 update(R,mid+1,r,x,y,z); 49 f[k]=min(f[L],f[R]); 50 } 51 int query(int k,int l,int r){ 52 if (l==r)return l; 53 down(k); 54 if (f[R])return query(R,mid+1,r); 55 return query(L,l,mid); 56 } 57 int main(){ 58 scanf("%d",&n); 59 for(int i=1;i<=n;i++)a[i]=read()+n-i; 60 memcpy(val,a,sizeof(val)); 61 sort(val+1,val+n+1); 62 m=unique(val+1,val+n+1)-val-1; 63 for(int i=1;i<=n;i++)a[i]=lower_bound(val+1,val+m+1,a[i])-val; 64 for(int i=1;i<=n;i++){ 65 update(1,1,m,1,a[i],-1); 66 if (i>1)update(1,1,m,a[i]+1,query(1,1,m),1); 67 b[i]=query(1,1,m); 68 } 69 for(int i=n-1;i;i--)b[i]=min(b[i],b[i+1]); 70 for(int i=1;i<=n;i++)ans+=abs(val[a[i]]-val[b[i]]); 71 printf("%lld\n",ans); 72 for(int i=1;i<=n;i++)write(val[b[i]]-n+i); 73 }
View Code