BZOJ 5125 小Q的书架 - 分治维护决策单调性dp - 学习笔记

决策单调性指的是,对于i< j,存在某个时刻k,满足i的转移不优于j,那么对于时刻t>k,i依然不会由于j。因此若将每个点的最优决策点(相同则最右)写下来,会是单调的。如果这个dp可以很快的计算一个决策点对于一个时刻的影响(即,如果转移方程是now[x]=min_{y < x}(pre[y]+calc(y+1,x)),而且calc函数可以很迅速的计算),那么可以用二分+单调栈维护。如果不是,但是影响可以类似莫队的办法增删两端的影响,就可以考虑分治维护。设solve(L,R,s,t)表示想要维护pre[L…R]对now[s…t]的影响,其中[L,R]表示对于所有的s<=x<=t,都有其最有决策点在[L,R]中。令mid=(s+t)/2,暴力找出mid的最优决策点p,然后用[L,p]更新[s,mid),右半部分同理。代码中的move(s,t)表示维护出calc(s,t)的答案v。

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<climits>
#define K 15
#define N 40010
#define lint long long
#define gc getchar()
#define INF 1000000000
#define debug(x) cerr<<#x<<"="<<x
#define sp <<" "
#define ln <<endl
using namespace std;
int n,k,l,r,v,b[N],dp[K][N],a[N];
inline void update(int x,int v)
{   for(;x<=n;x+=(x&-x)) b[x]+=v;    }
inline int query(int x,int ans=0)
{   for(;x;x-=(x&-x)) ans+=b[x];return ans; }
inline void move(int s,int t)
{
    while(r<t) r++,v+=r-l-query(a[r]),update(a[r],1);
    while(l>s) l--,v+=query(a[l]),update(a[l],1);
    while(l<s) update(a[l],-1),v-=query(a[l]),l++;
    while(r>t) update(a[r],-1),v-=r-l-query(a[r]),r--;
//  debug(s)sp,debug(t)sp,debug(v)ln;
}
int solve(int *pre,int *now,int L,int R,int s,int t)
{
    if(s>t) return 0;int mid=(s+t)>>1,x=L;now[mid]=INF;
    for(int i=L;i<=min(mid-1,R);i++)
        move(i+1,mid),(pre[i]+v<now[mid]?now[mid]=pre[x=i]+v:0);
//  debug(L)sp,debug(R)sp,debug(s)sp,debug(t)sp,debug(x)sp,debug(mid)sp,debug(now[mid])ln;
    return solve(pre,now,L,x,s,mid-1),solve(pre,now,x,R,mid+1,t);
}
inline int inn()
{
    int x,ch;while((ch=gc)<'0'||ch>'9');
    x=ch^'0';while((ch=gc)>='0'&&ch<='9')
        x=(x<<1)+(x<<3)+(ch^'0');return x;
}
int main()
{
    n=inn(),k=inn();
    for(int i=1;i<=n;i++)
        dp[1][i]=dp[1][i-1]+i-1-query(a[i]=inn()),update(a[i],1);
    for(int i=2;i<=k;i++)
        memset(b,0,sizeof(int)*(n+1)),v=0,
        l=1,r=0,solve(dp[i-1],dp[i],1,n,1,n);
    return !printf("%d\n",dp[k][n]);
}


猜你喜欢

转载自blog.csdn.net/mys_c_k/article/details/80114809
今日推荐