[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 }