按颜神犇PPT上分治树的思想,大胆考虑一个区间DP,发现可以写成序列形式,也就是
[f[i][j]=min{f[k][j-1]+cost[k+1][i]}
]
(f[i][j])表示前i个数,分成了(j)段,其中(cost[i][j])代表区间([i,j])的逆序对数
对于一个固定的(i)一个劣决策(k_1)永远会劣于一个好决策(k_2),即逆序对数减少速不会快于(k_2),具有显然的决策单调性
板板讲过,形如
[f[i]=min/max{g[j]+cost[j][i]}
]
的DP式,如果(cost)满足四边形不等式(也就是决策单调),那么不仅可以采用一般的单调栈维护决策单调性,还可以采用分治的方法,而且这种分治好写得多
更一般地,即使是(f[i][j])一样的状态,只要(f[i][j])只依赖(f[i][j-1]),那么它们本质上就是分开的(f)和(g),同样可以做
然而这里区间逆序对不会求,看了题解.....
题解采用了树状数组(log)时间维护单点,使用类似莫队move的方法,复杂度应该和分治有关,不会证,存疑
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
inline int rd(){
int ret=0,f=1;char c;
while(c=getchar(),!isdigit(c))f=c=='-'?-1:1;
while(isdigit(c))ret=ret*10+c-'0',c=getchar();
return ret*f;
}
const int MAXN = 40005;
int n,m,a[MAXN];
int t[MAXN];
int query(int x){
int ret=0;
for(int i=x;i;i-=i&-i)ret+=t[i];
return ret;
}
void update(int x,int w){
for(int i=x;i<=n;i+=i&-i)t[i]+=w;
}
int cur;
void move(int aiml,int aimr){
static int L=1,R;
while(L<aiml){update(a[L],-1);cur-=query(a[L]-1);L++;}
while(L>aiml){L--;update(a[L],1);cur+=query(a[L]-1);}
while(R<aimr){R++;update(a[R],1);cur+=(R-L+1)-query(a[R]);}
while(R>aimr){update(a[R],-1);cur-=(R-L)-query(a[R]-1);R--;}
}
int f[MAXN],g[MAXN];
void solve(int l,int r,int sl,int sr){
if(l>r)return;
int mid=(l+r)>>1,mn=1<<30,p=sl;
for(int i=sl;i<=sr&&i<mid;i++){
move(i+1,mid);
if(f[i]+cur<mn){mn=f[i]+cur;p=i;}
}
g[mid]=mn;
solve(l,mid-1,sl,p);
solve(mid+1,r,p,sr);
}
int main(){
n=rd();m=rd();
for(int i=1;i<=n;i++){a[i]=rd();}
for(int i=1;i<=n;i++){move(1,i);f[i]=cur;}
for(int i=1;i<m;i++){
solve(1,n,1,n);
memcpy(f,g,sizeof(g));
}
cout<<f[n];
return 0;
}