点此看题
首先有一个朴素 d p dp dp,设 d p [ i ] [ j ] dp[i][j] dp[i][j]为选出来 i i i个段,考虑到前 j j j位的最小顺序对个数:
d p [ i ] [ j ] = d p [ i − 1 ] [ k ] + c o s t ( k + 1 , j ) dp[i][j]=dp[i-1][k]+cost(k+1,j) dp[i][j]=dp[i−1][k]+cost(k+1,j)暴力做可以从后往前,每次向树状数组里面添加,然后算顺序对,时间复杂度 O ( n 2 k log n ) O(n^2k\log n) O(n2klogn)
有一个决策单调性,就是数要划分得尽量平均,也就是多考虑一个数决策点不会前移,证明:
其中灰色线段表示上一次的决策点, s 1 s1 s1表示红色段和黄色点产生的顺序对, s 2 s2 s2表示黄色点和绿色段产生的顺序对, s 3 s3 s3表示黄色点和新考虑的点的顺序对。由上一次的决策可知 s 1 < s 2 s1
怎么利用这个决策单调性呢?用一个类似整体二分的算法,函数 s o l v e ( l , r , t l , t r ) solve(l,r,tl,tr) solve(l,r,tl,tr)表示用 [ t l , t r ] [tl,tr] [tl,tr]的决策点去更新 [ l , r ] [l,r] [l,r]的 d p dp dp值,考虑处理出 m i d mid mid的最优决策点 p p p,那么 [ l , m i d − 1 ] [l,mid-1] [l,mid−1]用 [ t l , p ] [tl,p] [tl,p]来更新, [ m i d + 1 , r ] [mid+1,r] [mid+1,r]用 [ p , t r ] [p,tr] [p,tr]来更新,这样一直递归即可。还有一个问题,怎么算顺序对,可以用莫队的方法,因为对于每一层相邻段的移动和当前段的枚举都是 O ( n ) O(n) O(n)次的,所以复杂度有保证,总复杂度 O ( n k log n 2 ) O(nk\log n^2) O(nklogn2)
#include
#include
using namespace std;
const int M = 25005;
#define ll long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,k,l=1,r,a[M],b[M];ll f[30][M],ans;
int lowbit(int x)
{
return x&(-x);
}
void add(int x,int f)
{
for(;x<=n;x+=lowbit(x))
b[x]+=f;
}
int ask(int x)
{
int s=0;
for(;x>=1;x-=lowbit(x))
s+=b[x];
return s;
}
ll query(int L,int R)
{
while(L<l)
{
ans+=r-l+1;
ans-=ask(a[--l]);
add(a[l],1);
}
while(r<R)
{
ans+=ask(a[++r]);
add(a[r],1);
}
while(l<L)
{
add(a[l],-1);
ans+=ask(a[l++]);
ans-=r-l+1;
}
while(r>R)
{
add(a[r],-1);
ans-=ask(a[r--]);
}
return ans;
}
void solve(int x,int l,int r,int tl,int tr)
{
if(l>r || tl>tr) return ;
int mid=(l+r)>>1,p=0;
f[x][mid]=1e18;
for(int i=tl;i<=min(tr,mid-1);i++)
{
ll tmp=f[x-1][i]+query(i+1,mid);
if(tmp<f[x][mid]) f[x][mid]=tmp,p=i;
}
solve(x,l,mid-1,tl,p);
solve(x,mid+1,r,p,tr);
}
signed main()
{
n=read();k=read();
for(int i=1;i<=n;i++)
a[i]=read();
for(int i=1;i<=n;i++)
{
f[1][i]=f[1][i-1]+ask(a[i]);
add(a[i],1);
}
for(int i=1;i<=n;i++) b[i]=0;
for(int i=2;i<=k;i++)
solve(i,i,n,i-1,n);
printf("%lld\n",f[k][n]);
}