P5574

将长度为\(n\)的排列划分成\(K\)段,每段顺序对个数和最小

方程类似于邮局的式子

\(f[i][j]\)表示前\(i\)个数分\(j\)段最小值,\(w(i,j)\)表示区间\([i,j]\)的顺序对个数

\[f[i][j]=\min_{0\le k<i}\{f[k][j-1]+w(k+1,j)\} \]

不同的是邮局那道题可以直接预处理出\(w\),而这题不太好搞

树状数组暴力跳的话,可以用莫队思想将答案推至相邻区间

\(O(n^2k\log n)\) \(50\%\) 好成绩

因为有\(w(i,j+1)+w(i+1,j)< w(i,j)+w(i+1,j+1)\)

(画个图可知,扔到数轴上,发现无论如何都是严格单调的,并且可以和暴力拍上)

\(w\)满足四边形不等式具有决策单调性

然后枚举\(j\),设\(g[i]\)\(f[i][j]\)的最优决策点,\(f[i][j]=f[g[i]][j-1]+w[g[i]+1][i]\)

同样有\(g[i-1]\le g[i]\)

根据单调性,并且\(w\)无法快速求得,考虑分治维护

\(solve(l,r,L,R)\)表示从\([L,R]\)转移到\([l,r]\),令\(mid=(l+r)/2\)

暴力找到\(mid\)\([L,R]\)的决策点\(x\),根据单调性,\([l,mid-1]\)\([L,x]\)转移,\([mid+1,r]\)这段区间用\([x,R]\)转移,暴力跳树状数组均摊复杂度\(n\log n\),一共分治\(\log\)层,求解\(k\)次,\(O(nk\log^2n)\) 可过

int f[N][26],a[N],c[N],n,K,sum,L,R,now;
inline int min(int a,int b){return a < b ? a : b;} 
void add(int k,int x){for(;k <= n;k += k&-k) c[k] += x;}
int query(int x){
	int res = 0;
	for(;x;x-=x&-x) res += c[x];
	return res;
}
void change(int l,int r){//暴力反复纵跳 
	while(L < l) sum -= query(a[L]-1),add(a[L++],-1);
	while(L > l) sum += query(a[L-1]-1),add(a[--L],1);
    while(R < r) sum += R-L+1 - query(a[R+1]),add(a[++R],1);
    while(R > r) sum -= R-L+1 - query(a[R]),add(a[R--],-1);
}
void solve(int l,int r,int L,int R){
	if(l > r)return;
    int mid = l + r >> 1,x = L;
    for(int i = L;i <= min(mid-1,R);i++){
        change(i+1,mid);
        int ans = f[i][now-1]+sum;
        if(ans < f[mid][now])
            f[mid][now] = ans,x = i;//寻找最优决策点
    }
    solve(l,mid-1,L,x); solve(mid+1,r,x,R);
} 
int main(){
	scanf("%d%d",&n,&K);
	for(int i = 1;i <= n;++i) scanf("%d",&a[i]),a[i] = n-a[i]+1;
	L = 1; /*左指针*/memset(f,0x3f,sizeof(f)); 
	for(int i = 1;i <= n;++i)
		change(1,i),f[i][1] = sum;
	for(now = 2;now <= K;++now)
		solve(1,n,1,n);
	printf("%d",f[n][K]);
}
posted @ 2020-10-16 10:12  INFP  阅读(54)  评论(0编辑  收藏  举报