SP16809 EST - Estimation

Link\text{Link}

题意

给你一个长度为 nn 整数数组 aa, 求一个同长数组 bb,其中 bb 分为 kk 段, 每段中 bib_i 的值相等,即当 i,ji,j 同属某一段时,有 bi=bjb_i=b_j,使得 i=1naibi\sum_{i=1}^n|a_i-b_i| 最小,输出这个最小值。

分析

要使一段 [l,r][l,r] 取到最小值,显然要使 bk(lkr)b_k(l\le k\le r)alra_{l\sim r} 的中位数,可以借鉴 P1168 的思想,使用对顶堆来动态维护或者预处理,求出 alra_{l\sim r} 的中位数 amida_{mid}

cost(l,r)cost(l,r) 为将区间 [l,r][l,r] 分为一段后的 i=lraiamid\sum_{i=l}^r|a_i-a_{mid}|,如何求呢?我们把绝对值拆成非负和负两部分,先用两个树状数组存下 v1=i=lr[aiamid],v2=i=lr[aiamid]×aiv1=\sum_{i=l}^r[a_i\le a_{mid}],v2=\sum_{i=l}^r[a_i\le a_{mid}]\times a_i,那么 cost(l,r)=(v1×amidv2)+[i=lraiv2(rl+1v1)×amid]cost(l,r)=(v1\times a_{mid}-v2)+[\sum_{i=l}^ra_i-v2-(r-l+1-v1)\times a_{mid}]

dpi,pdp_{i,p} 为将 a1ia_{1\sim i} 划分为 pp 段的最小值,接下来状态转移方程就十分好列了:

dpi,p=maxj=i10{dpj,p1+cost(j+1,i)}dp_{i,p}=\max_{j=i-1}^0\{dp_{j,p-1}+cost(j+1,i)\}

注意 jj 层循环是倒序的,这样方便动态维护中位数和树状数组。

时间复杂度 O(n2(k+logn))O(n^2(k+\log n))

代码

#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;
}
posted @ 2022-04-16 23:43  luckydrawbox  阅读(9)  评论(0)    收藏  举报  来源