SP16809 EST - Estimation
题意
给你一个长度为 整数数组 , 求一个同长数组 ,其中 分为 段, 每段中 的值相等,即当 同属某一段时,有 ,使得 最小,输出这个最小值。
分析
要使一段 取到最小值,显然要使 为 的中位数,可以借鉴 P1168 的思想,使用对顶堆来动态维护或者预处理,求出 的中位数 。
设 为将区间 分为一段后的 ,如何求呢?我们把绝对值拆成非负和负两部分,先用两个树状数组存下 ,那么 。
设 为将 划分为 段的最小值,接下来状态转移方程就十分好列了:
注意 层循环是倒序的,这样方便动态维护中位数和树状数组。
时间复杂度 。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
long long read(){
long long x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
void write(long long x){
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int N=2010,K=30;
int n,k;
ll dp[N][K],a[N],b[N],c[2][N],tot,mid,v,v1,v2;
void add(int t,int x,ll v){//树状数组
for(;x<=n;x+=x&-x)
c[t][x]+=v;
}
ll ask(int t,int x){
ll ans=0;
for(;x;x-=x&-x)
ans+=c[t][x];
return ans;
}
priority_queue<int>q1,q2;
void addq(int p,int x){//对顶堆
if(q1.size()&&q1.top()<x)
q2.push(-x);
else q1.push(x);
while(q1.size()<p){
q1.push(-q2.top());
q2.pop();
}
while(q1.size()>p){
q2.push(-q1.top());
q1.pop();
}
}
int main(){
while(n=read()){
k=read();
k=min(k,n);
for(int i=1;i<=n;i++)
b[i]=a[i]=read();
sort(b+1,b+n+1);
tot=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+tot+1,a[i])-b;
memset(dp,0x3f,sizeof(dp));
dp[0][0]=0;
for(int i=1;i<=n;i++){
memset(c,0,sizeof(c));//清空树状数组和对顶堆
while(q1.size())
q1.pop();
while(q2.size())
q2.pop();
for(int j=i-1;j>=0;j--){
addq((i-j+1)/2,a[j+1]);
add(0,a[j+1],1);
add(1,a[j+1],b[a[j+1]]);
mid=q1.top();
v1=ask(0,mid);
v2=ask(1,mid);
v=(b[mid]*v1-v2)+(ask(1,tot)-v2-b[mid]*(i-j-v1));//计算cost
for(int p=1;p<=k&&p<=i;p++)
dp[i][p]=min(dp[i][p],dp[j][p-1]+v);
}
}
write(dp[n][k]);
puts("");
}
return 0;
}

浙公网安备 33010602011771号