1. 程式人生 > 實用技巧 >[luogu5574]任務分配問題

[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