[BZOJ] 5125: [Lydsy1712月赛]小Q的书架

按颜神犇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;
}

未经许可,禁止搬运。
原文地址:https://www.cnblogs.com/ghostcai/p/9791518.html