斜率优化

斜率优化

问题描述

羊羊列队
(File IO): input:queue.in output:queue.out
时间限制: 1000 ms 空间限制: 262144 KB

题目描述
在修建完新路后,小羊们总算可以安心入学了。今年是羊年,新入学的小羊特别多。老师们打算将N只小羊分成M个班级,每个班至少有1只羊。
如何分班成了老师们最头疼的事情,因为开学典礼上,村长就要看到小羊们列队的情况。每个班的小羊都排成一排,站在草场上。村长希望队列中羊的高度尽可能整齐,村长对队列的不整齐度有自己的要求。
例如队列中共有t只羊,高度依次为A1,A2……,At。那么不整齐度为:(|A1-A2|+|A2-A3|+……+|At-1-At|)^2。即相邻两只羊高度差之和的平方。
而总体的不整齐度,就是各班不整齐度之和。
现在,请你帮助老师们设计一下,如何分班,如何列队,才能使M个班级的不整齐度之和最小。

输入
第一行两个整数N和M,分别表示共有N只小羊,要被分成M个班级。
第二行N个整数,表示每只小羊的高度Ai。

输出
输出最小的不整齐度之和,结果保证不会超过2^31-1。

样例输入
4 2
4 1 3 2

样例输出
2

数据范围限制
30%的数据,1<=N<=10;1<=M<=5;
80%的数据,1<=N<=300;1<=Ai<=1000;
100%的数据,1<=N<=10000,1<=M<=1000,1<=Ai<=1000000,保证M<=N。

提示
分成两班,4和3一个班,1和2一个班,不管怎么排,两个班的不整齐度都是1,不整齐度之和为2。

关于引题题解

这道题应该比较基础。
可以很容易的看出来,我们需要先对Ai进行排序。
最优的方案肯定是在排过序的Ai上,从左往右进行分班。
紧接着,我们可以设 f [ i ] [ j ] f[i][j] f[i][j]表示前i个人被分在前j个班中的最小答案。
我们就有了方程 f [ i ] [ j ] = f [ k ] [ j − 1 ] + ( ( k + 1 ) 至 ( i ) 所 花 费 的 代 价 ) f[i][j]=f[k][j-1]+((k+1)至(i)所花费的代价) f[i][j]=f[k][j1]+((k+1)(i))
就是: f [ i ] [ j ] = M i n ( f [ k ] [ j − 1 ] + ( a [ i ] − a [ k + 1 ] ) ∗ ( a [ i ] − a [ k + 1 ] ) ) f[i][j]=Min(f[k][j-1]+(a[i]-a[k+1])*(a[i]-a[k+1])) f[i][j]=Min(f[k][j1]+(a[i]a[k+1])(a[i]a[k+1]))
答案就是 f [ n ] [ m ] f[n][m] f[n][m]
不过,这样做的时间复杂度是 O ( n 2 ∗ m ) O(n^2*m) O(n2m),极限数据来讲,效率不高。

如何优化

我们考虑斜率优化。
(为什么不是单调队列呢?)
(因为单调队列必须保证转移式子化简后,极值部分与当前状态无关)
接下来,类似单调队列的思想,我们想想转移过来的状态有没有单调性。
设一个可能可以转移过来的状态为k1,另一个为k2,并且保证k1>k2。
于是,我们考虑从k1转过来要优与k2.
f [ k 1 ] [ j − 1 ] + ( a [ i ] − a [ k 1 + 1 ] ) 2 &lt; f [ k 2 ] [ j − 1 ] + ( a [ i ] − a [ k 2 + 1 ] ) 2 f[k1][j-1]+(a[i]-a[k1+1])^2&lt;f[k2][j-1]+(a[i]-a[k2+1])^2 f[k1][j1]+(a[i]a[k1+1])2<f[k2][j1]+(a[i]a[k2+1])2
f [ k 1 ] [ j − 1 ] + a [ i ] 2 − 2 ∗ a [ i ] ∗ a [ k 1 + 1 ] + a [ k 1 + 1 ] 2 &lt; f [ k 2 ] [ j − 1 ] + a [ i ] 2 − 2 ∗ a [ i ] ∗ a [ k 2 + 1 ] + a [ k 2 + 1 ] 2 f[k1][j-1]+a[i]^2-2*a[i]*a[k1+1]+a[k1+1]^2&lt;f[k2][j-1]+a[i]^2-2*a[i]*a[k2+1]+a[k2+1]^2 f[k1][j1]+a[i]22a[i]a[k1+1]+a[k1+1]2<f[k2][j1]+a[i]22a[i]a[k2+1]+a[k2+1]2
两边同时约去 a [ i ] 2 a[i]^2 a[i]2
f [ k 1 ] [ j − 1 ] − 2 ∗ a [ i ] ∗ a [ k 1 + 1 ] + a [ k 1 + 1 ] 2 &lt; f [ k 2 ] [ j − 1 ] − 2 ∗ a [ i ] ∗ a [ k 2 + 1 ] + a [ k 2 + 1 ] 2 f[k1][j-1]-2*a[i]*a[k1+1]+a[k1+1]^2&lt;f[k2][j-1]-2*a[i]*a[k2+1]+a[k2+1]^2 f[k1][j1]2a[i]a[k1+1]+a[k1+1]2<f[k2][j1]2a[i]a[k2+1]+a[k2+1]2
移项
f [ k 1 ] [ j − 1 ] + a [ k 1 + 1 ] 2 − f [ k 2 ] [ j − 1 ] − a [ k 2 + 1 ] 2 &lt; 2 ∗ a [ i ] ∗ ( a [ k 1 + 1 ] − a [ k 2 + 1 ] ) f[k1][j-1]+a[k1+1]^2-f[k2][j-1]-a[k2+1]^2&lt;2*a[i]*(a[k1+1]-a[k2+1]) f[k1][j1]+a[k1+1]2f[k2][j1]a[k2+1]2<2a[i](a[k1+1]a[k2+1])
( f [ k 1 ] [ j − 1 ] + a [ k 1 + 1 ] 2 ) − ( f [ k 2 ] [ j − 1 ] + a [ k 2 + 1 ] 2 ) a [ k 1 + 1 ] − a [ k 2 + 1 ] &lt; 2 ∗ a [ i ] \frac{(f[k1][j-1]+a[k1+1]^2)-(f[k2][j-1]+a[k2+1]^2)}{a[k1+1]-a[k2+1]}&lt;2*a[i] a[k1+1]a[k2+1](f[k1][j1]+a[k1+1]2)(f[k2][j1]+a[k2+1]2)<2a[i]
观察式子,发现此不等式很像斜率式。
于是我们可以以 ( a [ i + 1 ] , f [ i ] [ j ] + a [ i + 1 ] 2 ) (a[i+1],f[i][j]+a[i+1]^2) (a[i+1],f[i][j]+a[i+1]2)为坐标在平面直角坐标系上挂上一个(i,j)的点。
因此,若两个点之间的斜率满足 &lt; 2 ∗ a [ i ] &lt;2*a[i] <2a[i]时,那么前一个点在这一次转移中是没有作用的(劣与后一个点),当然,否则后一个点是没有用的。

关于式子的转换大概就这么多。
关键在于这有什么用。
如果斜率满足单调性的话,一开始我们的想法就成立了。
将相邻两个待转移的点直接连上线。
于是我们试图求这些待转移的点的凸包。
我们试图维护下凸壳。
如果假设存在(i,j)与(j,k)。如果(i,j,k)构成上凸,那么就有 x l ( i , j ) &gt; x l ( j , k ) ( x l 表 示 斜 率 ) xl(i,j)&gt;xl(j,k)(xl表示斜率) xl(i,j)>xl(j,k)(xl),我们假设 p 1 = x l ( i , j ) p1=xl(i,j) p1=xl(i,j) p 2 = x l ( j , k ) p2=xl(j,k) p2=xl(j,k)。设 p 3 = a [ i ] ∗ 2 p3=a[i]*2 p3=a[i]2
假设p1>p3>p2,那么我们可以得到j是无用的。
假设p1>p2>p3,那么我们可以得到j与k是无用的。
假设p3>p1>p2,那么我们可以得到j与i是无用的。
我们发现,不管什么情况,j好像始终的是没有用的,所以我们可以将j删去。

也就是说,如果在一个凸包中存在上凸,那么中间的那一个决策点是不管怎样都没有作用的。所以我们的下凸壳维护成功!维护下凸壳我们可以用栈进行维护。

这里,我们就得到一个很优秀的性质,那就是斜率满足单调性!
考虑最优决策点。我们发现,当决策线(i,j)满足 x l ( i , j ) &lt; 2 ∗ a [ i ] xl(i,j)&lt;2*a[i] xl(i,j)<2a[i]时,i决策点要劣于j,当 x l ( i , j ) &gt; = 2 ∗ a [ i ] xl(i,j)&gt;=2*a[i] xl(i,j)>=2a[i]时,i决策点要优于(或等于)j。所以我们只需要取到满足 x l ( i − 1 , i ) &lt; 2 ∗ a [ i ] xl(i-1,i)&lt;2*a[i] xl(i1,i)<2a[i] x l ( i , i + 1 ) &gt; = 2 ∗ a [ i ] xl(i,i+1)&gt;=2*a[i] xl(i,i+1)>=2a[i]时,i就是最小的决策点。
所以我们可以二分解决最小决策点的找寻问题。

这样的时间复杂度是: O ( n ∗ l o g ( n ) ∗ m ) O(n*log(n)*m) O(nlog(n)m)

再度优化

我们发现这样子还是会超时,怎么办呢?
我们发现这里的2*a[i]是递增的。所以,我们可以开一个队列来维护。
队列的实现 ->https://www.cnblogs.com/tham/p/8038828.html

代码

二分求法(80分TLE)
#include<cstdio>
#include<cstring>
#define N 5001
#define M 1001
using namespace std;
int a[N],s[N],f[N][M];
double last[N][M];
int n,m,zhan[N][M][3],dec[N][M],top[N];
int abs(int x)
{
	if (x<0) return -x;
	return x;
}
int min(int x,int y)
{
	if (x<y) return x;
	return y;
}
double xl(double x1,double y1,double x2,double y2)
{
	if (x2==x1) return 1e9;
	return (y2-y1)/(x2-x1);
}
void qsort(int l,int r)
{
	int i=l,j=r,mid=a[(i+j)/2];
	while (i<=j)
	{
		while (a[i]<mid) i++;
		while (a[j]>mid) j--;
		if (i<=j)
		{
			int t=a[i];
			a[i]=a[j];
			a[j]=t;
			i++;j--;
		}
	}
	if (i<r) qsort(i,r);
	if (l<j) qsort(l,j);
}
int main()
{
	freopen("queue.in","r",stdin);
	freopen("queue.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	qsort(1,n);
//	for (int i=1;i<=n;i++) printf("%d ",a[i]);
//	printf("\n");
	for (int i=2;i<=n;i++) s[i]=s[i-1]+abs(a[i]-a[i-1]);
	s[n+1]=s[n];
	for (int i=0;i<=n;i++) 
		for (int j=0;j<=m;j++) f[i][j]=1e9;
	f[0][0]=0;
	top[0]=1;zhan[0][1][1]=0;zhan[0][1][2]=0;
	memset(last,0,sizeof(last));
	dec[0][0]=0;
	for (int k1=1;k1<=m;k1++)
	{
		top[k1]=1;
		zhan[k1][top[k1]][1]=0;
		zhan[k1][top[k1]][2]=0;
		dec[k1][top[k1]]=1;
		for (int i=1;i<=n;i++)
		{
			int l=1,r=top[k1-1],ans=-1;
			while (l<=r)
			{
				int mid=(l+r)/2;
				if (last[k1-1][mid]<=2*s[i]&&dec[k1-1][mid]-1<=i)
				{
					ans=dec[k1-1][mid];
					l=mid+1;
				}
				else	r=mid-1;
			}
			if (ans<0)
			{
				f[i][k1]=1e9;
				continue;
			}
			else
				f[i][k1]=f[ans-1][k1-1]+(s[i]-s[ans])*(s[i]-s[ans]);
			if (i<k1)
				f[i][k1]=0;
			int newx=s[i+1];
			int newy=f[i][k1]+s[i+1]*s[i+1];
			while (top[k1]>=2&&xl(zhan[k1][top[k1]][1],zhan[k1][top[k1]][2],newx,newy)<last[k1][top[k1]])
				top[k1]--;
			last[k1][top[k1]+1]=xl(zhan[k1][top[k1]][1],zhan[k1][top[k1]][2],newx,newy);	
			top[k1]++;
			zhan[k1][top[k1]][1]=newx;
			zhan[k1][top[k1]][2]=newy;
			dec[k1][top[k1]]=i+1;		
		}
	}	
/*	for (int k=1;k<=m;k++)	
		for (int i=1;i<=n;i++)
				for (int j=1;j<=i;j++)
					f[i][k]=min(f[i][k],f[j-1][k-1]+(s[i]-s[j])*(s[i]-s[j]));*/
	printf("%d",f[n][m]);
}


单调队列+二分(80分TLE)
有时很优秀
#include<cstdio>
#include<cstring>
#define N 10001
#define M 20001
using namespace std;
int a[N],s[N],f[N][3];
double last[3][M];
int n,m,zhan[3][M][3],dec[3][M],top[3],tail[3];
int abs(int x)
{
	if (x<0) return -x;
	return x;
}
int min(int x,int y)
{
	if (x<y) return x;
	return y;
}
double xl(double x1,double y1,double x2,double y2)
{
	if (x2==x1) 
		if (y2<y1)	return -1e9;
		else	return 1e9;
	return (y2-y1)/(x2-x1);
}
void qsort(int l,int r)
{
	int i=l,j=r,mid=a[(i+j)/2];
	while (i<=j)
	{
		while (a[i]<mid) i++;
		while (a[j]>mid) j--;
		if (i<=j)
		{
			int t=a[i];
			a[i]=a[j];
			a[j]=t;
			i++;j--;
		}
	}
	if (i<r) qsort(i,r);
	if (l<j) qsort(l,j);
}
int main()
{
	freopen("queue.in","r",stdin);
	freopen("queue.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	qsort(1,n);
	for (int i=2;i<=n;i++) s[i]=s[i-1]+abs(a[i]-a[i-1]);
	s[n+1]=s[n];
	for (int j=0;j<=m;j++) f[j][0]=1e9;
	f[0][0]=0;
	top[0]=1;zhan[0][1][1]=0;zhan[0][1][2]=0;
	memset(last,0,sizeof(last));
	dec[0][0]=0;
	long long o=0;
	for (int k1=1;k1<=m;k1++)
	{
		int now=k1%2;
		int lasts=(k1+1)%2;
		top[now]=1;
		zhan[now][top[now]][1]=0;
		zhan[now][top[now]][2]=0;
		dec[now][top[now]]=1;
		tail[lasts]=1;
		for (int i=1;i<=n;i++) f[i][now]=1e9; 
		for (int i=1;i<=n;i++)
		{
			int l=tail[lasts],r=top[lasts],ans=0;
			while (l<=r)
			{
				int mid=(l+r)/2;
				if (last[lasts][mid]<=2*s[i])
				{
					ans=dec[lasts][mid];
					tail[lasts]=mid;
					l=mid+1;
				}
				else
					r=mid-1;
			}
			if (tail[lasts]>top[lasts]) 
				ans=dec[lasts][top[lasts]];
			if (ans<0&&i>k1)
			{
				f[i][now]=1e9;
				continue;
			}
			else
				if (ans>=1)
					f[i][now]=f[ans-1][lasts]+(s[i]-s[ans])*(s[i]-s[ans]);
			if (ans==0) f[i][now]=(s[i]-s[0])*(s[i]-s[0]);
			if (i<=k1)
				f[i][now]=0;
			double newx=s[i+1];
			double newy=f[i][now]+s[i+1]*s[i+1];
			while (top[now]>=2&&xl(zhan[now][top[now]][1],zhan[now][top[now]][2],newx,newy)<last[now][top[now]])
			{
				top[now]--;
			}
			last[now][top[now]+1]=xl(zhan[now][top[now]][1],zhan[now][top[now]][2],newx,newy);	
			top[now]++;
			zhan[now][top[now]][1]=newx;
			zhan[now][top[now]][2]=newy;
			dec[now][top[now]]=i+1;		
		}
	}
	printf("%d",f[n][m%2]);
}
纯单调队列(AC 768 ms)
#include<cstdio>
#include<cstring>
#define N 10001
#define M 20001
using namespace std;
int a[N],s[N],f[N][3];
double last[3][M];
int n,m,zhan[3][M][3],dec[3][M],top[3],tail[3];
int abs(int x)
{
	if (x<0) return -x;
	return x;
}
int min(int x,int y)
{
	if (x<y) return x;
	return y;
}
double xl(double x1,double y1,double x2,double y2)
{
	if (x2==x1) 
		if (y2<y1)	return -1e9;
		else	return 1e9;
	return (y2-y1)/(x2-x1);
}
void qsort(int l,int r)
{
	int i=l,j=r,mid=a[(i+j)/2];
	while (i<=j)
	{
		while (a[i]<mid) i++;
		while (a[j]>mid) j--;
		if (i<=j)
		{
			int t=a[i];
			a[i]=a[j];
			a[j]=t;
			i++;j--;
		}
	}
	if (i<r) qsort(i,r);
	if (l<j) qsort(l,j);
}
int main()
{
	freopen("queue.in","r",stdin);
	freopen("queue.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]);
	qsort(1,n);
	for (int i=2;i<=n;i++) s[i]=s[i-1]+abs(a[i]-a[i-1]);
	s[n+1]=s[n];
	for (int j=0;j<=m;j++) f[j][0]=1e9;
	f[0][0]=0;
	top[0]=1;zhan[0][1][1]=0;zhan[0][1][2]=0;
	memset(last,0,sizeof(last));
	dec[0][0]=0;
	long long o=0;
	for (int k1=1;k1<=m;k1++)
	{
		int now=k1%2;
		int lasts=(k1+1)%2;
		top[now]=1;
		zhan[now][top[now]][1]=0;
		zhan[now][top[now]][2]=0;
		dec[now][top[now]]=1;
		tail[lasts]=1;
		for (int i=1;i<=n;i++) f[i][now]=1e9; 
		for (int i=1;i<=n;i++)
		{
			int l=tail[lasts],r=top[lasts],ans=0;
			while (l<=r)
			{
				int mid=l;
				if (last[lasts][mid]<=2*s[i]&&last[lasts][mid+1]>2*s[i])
				{
					ans=dec[lasts][mid];
					tail[lasts]=mid;
					break;
				}
				l++;
			}
			if (tail[lasts]>top[lasts]) 
				ans=dec[lasts][top[lasts]];
			if (ans<0&&i>k1)
			{
				f[i][now]=1e9;
				continue;
			}
			else
				if (ans>=1)
					f[i][now]=f[ans-1][lasts]+(s[i]-s[ans])*(s[i]-s[ans]);
			if (ans==0) f[i][now]=(s[i]-s[0])*(s[i]-s[0]);
			if (i<=k1)
				f[i][now]=0;
			double newx=s[i+1];
			double newy=f[i][now]+s[i+1]*s[i+1];
			while (top[now]>=2&&xl(zhan[now][top[now]][1],zhan[now][top[now]][2],newx,newy)<last[now][top[now]])
			{
				top[now]--;
			}
			last[now][top[now]+1]=xl(zhan[now][top[now]][1],zhan[now][top[now]][2],newx,newy);	
			top[now]++;
			zhan[now][top[now]][1]=newx;
			zhan[now][top[now]][2]=newy;
			dec[now][top[now]]=i+1;		
		}
	}
	printf("%d",f[n][m%2]);
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值