[luogu5574]任務分配問題
首先暴力dp,令$f_{i,j}$表示前$i$個點劃分為$j$段,即有轉移$f_{i,j}=\min f_{k-1,j-1}+calc(k,i)$(其中$calc(i,j)$表示求區間$[i,j]$的順序對數)
可以先列舉$j$,記$g_{i}=f_{i,j-1}$,則$f_{i}=\min g_{k-1}+calc(k,i)$,先$o(k)$列舉以下層數,快速支援上述轉移
令$mn_{i}$為取到最小值的最小的$k$,即有$f_{i}=g_{mn_{i}-1}+calc(mn_{i},i)$,則有$mn_{i}\le mn_{i+1}$(即決策單調性),證明如下:
反證法,若$mn_{i}>mn_{i+1}$(為方便表示,以下記$x=mn_{i}$、$y=mn_{i+1}$),則由於其都是最小的轉移,則有:$g_{x-1}+calc(x,i)\le g_{y-1}+calc(y,i)$,$g_{x-1}+calc(x,i+1)\ge g_{y-1}+calc(y,i+1)$
將第二個式子乘上-1後與第一個式子分別相加,可得$calc(x,i)-calc(x,i+1)\le calc(y)-calc(y,i+1)$
考慮順序對的意義(即代入順序對的式子),即$-\sum_{j=x}^{i}[a_{j}\le a_{i+1}]\le -\sum_{j=y}^{i}[a_{j}\le a_{i+1}]$
由於$x>y$,將右式加過來,即$\sum_{j=y}^{x-1}[a_{j}\le a_{i+1}]\le 0$,由於左式非負,因此必然取等號
考慮這個式子是由最初兩個式子相加,因此也應取到等號,即$g_{x-1}+calc(x,i)=g_{y-1}+calc(y,i)$,這與$mn_{i}$為最小的$k$矛盾
接下來考慮如何來維護這個$mn_{i}$,直接整體二分,即求出$mn_{mid}$,然後劃分為兩部分即可
但還有一個問題,考慮如何求$mn_{mid}$,假設詢問區間為$[l,r]$,答案(即$mn_{i}$)對應區間為$[x,y]$,此時如果暴力求$[y,mid]$內的順序對數複雜度顯然是不對的,因此考慮優化
類似莫隊,維護一個區間$[l',r']$以及該區間內的順序對數,之後通過移動$l'$和$r'$(需要可持久化線段樹維護移動)來得到該區間,接下來每次移動次數為$o((y-x)+(r-l))$,由此即可得總複雜度為$o(nk\log^{2}n)$
首先,每一次開始時,$l'\in [x,y]$且$r'\in [l,r]$(可以歸納),此時相當於要將$r'$移動到$mid$,再將$l'$移動到$y$再移動回$x$,這些年都是$o((y-x)+(r-l))$的
之後考慮將$l'$移動回到$mn_{mid}$,進行搜尋左區間,再將$r'$移動到$mid+1$來搜尋右區間,最後再把$l'$和$r'$移動回最開始的狀態,這樣就可以保證複雜度
(由於$|a-b|+|b-c|\ge |a-c|$,這個移動並不需要去實現,而只是證明覆雜度)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 25005 4 #define mid (l+r>>1) 5 int V,n,k,ll,rr,a[N],rt[N],tr[N*20],ls[N*20],rs[N*20]; 6 long long sum,g[N],f[N]; 7 int New(int k){ 8 tr[++V]=tr[k]; 9 ls[V]=ls[k]; 10 rs[V]=rs[k]; 11 return V; 12 } 13 void update(int &k,int l,int r,int x){ 14 k=New(k); 15 tr[k]++; 16 if (l==r)return; 17 if (x<=mid)update(ls[k],l,mid,x); 18 else update(rs[k],mid+1,r,x); 19 } 20 int query(int k,int l,int r,int x,int y){ 21 if ((!k)||(l>y)||(x>r))return 0; 22 if ((x<=l)&&(r<=y))return tr[k]; 23 return query(ls[k],l,mid,x,y)+query(rs[k],mid+1,r,x,y); 24 } 25 int calcl(int x,int y){ 26 return query(rt[y],1,n,a[x]+1,n)-query(rt[x],1,n,a[x]+1,n); 27 } 28 int calcr(int x,int y){ 29 return query(rt[y-1],1,n,1,a[y]-1)-query(rt[x-1],1,n,1,a[y]-1); 30 } 31 long long calc(int x,int y){ 32 while (rr<y)sum+=calcr(ll,++rr); 33 while (x<ll)sum+=calcl(--ll,rr); 34 while (ll<x)sum-=calcl(ll++,rr); 35 while (y<rr)sum-=calcr(ll,rr--); 36 return sum; 37 } 38 void dfs(int l,int r,int x,int y){ 39 if (l>r)return; 40 int s=0,k=0; 41 f[mid]=0x3f3f3f3f; 42 for(int i=x;i<=min(mid,y);i++){ 43 int s=g[i-1]+calc(i,mid); 44 if (s<f[mid]){ 45 f[mid]=s; 46 k=i; 47 } 48 } 49 dfs(l,mid-1,x,k); 50 dfs(mid+1,r,k,y); 51 } 52 int main(){ 53 scanf("%d%d",&n,&k); 54 for(int i=1;i<=n;i++)scanf("%d",&a[i]); 55 for(int i=1;i<=n;i++){ 56 rt[i]=rt[i-1]; 57 update(rt[i],1,n,a[i]); 58 } 59 ll=1,rr=0; 60 for(int i=1;i<=n;i++)f[i]=calc(1,i); 61 for(int i=1;i<k;i++){ 62 memcpy(g,f,sizeof(g)); 63 dfs(1,n,1,n); 64 } 65 printf("%d",f[n]); 66 }View Code