Loj#2035-[SDOI2016]征途【斜率优化】

正题

题目链接:https://loj.ac/problem/2035


题目大意

n n 个数字分成 m m 段,要求方差最小。


解题思路

首先方差的公式 i = 1 n ( x i x ) 2 \sum_{i=1}^n(x_i-|x|)^2
其中 x |x| 是不变的,定义 w = x w=|x|
f i , j f_{i,j} 表示已经分到第 i i 段,到第 j j 个时的最小方差和。

做前缀和 s i = j = 1 i a i s_i=\sum_{j=1}^ia_i
之后有 f k , i = m i n { f k 1 , j + ( s i s j ) 2 + w 2 2 ( s i s j ) w } f_{k,i}=min\{f_{k-1,j}+(s_i-s_j)^2+w^2-2(s_i-s_j)w\}
去掉 m i n min 拆括号
f k , i = f k 1 , j + s i 2 2 s i s j + s j 2 + w 2 2 s i w + s j w f_{k,i}=f_{k-1,j}+s_i^2-2s_is_j+s_j^2+w^2-2s_iw+s_jw
f k , i s i 2 + s i w + 2 s i s j 2 s j w = f k 1 , j + s j 2 f_{k,i}-s_i^2+s_iw+2s_is_j-2s_jw=f_{k-1,j}+s_j^2
f k , i f_{k,i} 最小就是 f k , i s i 2 + s i w f_{k,i}-s_i^2+s_iw 最小,后为了方便
定义 F = f k , i s i 2 + s i w F=f_{k,i}-s_i^2+s_iw
F + 2 ( s i w ) s j = f k 1 , j + s j 2 F+2(s_i-w)s_j=f_{k-1,j}+s_j^2
然后有若干个决策点 ( s j , f k 1 , j + s j 2 ) (s_j,f_{k-1,j}+s_j^2)
每次有一条直线 y = 2 ( s i w ) x + F y=2(s_i-w)x+F 经过某个决策点要求 F F 最小
显然因为 s i w s_i-w 的单调性和 s j s_j 的单调性我们可以使用单调队列维护一个下凸壳。

时间复杂度 O ( n m ) O(nm)


c o d e code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define pow2(x) ((x)*(x))
using namespace std;
const int N=3100;
struct node{
	double x,y;
	int num;
}q[N];
int n,m;
double s[N],f[N][N];
double slope(node x,node y)
{return (y.y-x.y)/(y.x-x.x);}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lf",&s[i]),s[i]=s[i]*m+s[i-1];
	double w=s[n]/m;
	for(int i=1;i<=n;i++)
		f[1][i]=pow2(s[i]-w);
	for(int k=2;k<=m;k++){
		int head=1,tail=1;
		q[1]=(node){s[k-1],f[k-1][k-1]+pow2(s[k-1]),k-1};
		for(int i=k;i<=n;i++){
			int z=2*(s[i]-w);
			while(head<tail&&slope(q[head],q[head+1])<z)head++;
			int p=q[head].num;
			f[k][i]=f[k-1][p]+pow2(s[i]-s[p]-w);
			node po=(node){s[i],f[k-1][i]+pow2(s[i]),i};
			while(head<tail&&slope(po,q[tail])<slope(q[tail-1],q[tail]))
				tail--;
			q[++tail]=po;
		}
	}
	printf("%.0lf",f[m][n]/m);
}

猜你喜欢

转载自blog.csdn.net/Mr_wuyongcong/article/details/106599980
今日推荐