斜率优化
问题描述
羊羊列队
(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][j−1]+((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][j−1]+(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(n2∗m),极限数据来讲,效率不高。
如何优化
我们考虑斜率优化。
(为什么不是单调队列呢?)
(因为单调队列必须保证转移式子化简后,极值部分与当前状态无关)
接下来,类似单调队列的思想,我们想想转移过来的状态有没有单调性。
设一个可能可以转移过来的状态为k1,另一个为k2,并且保证k1>k2。
于是,我们考虑从k1转过来要优与k2.
有
f
[
k
1
]
[
j
−
1
]
+
(
a
[
i
]
−
a
[
k
1
+
1
]
)
2
<
f
[
k
2
]
[
j
−
1
]
+
(
a
[
i
]
−
a
[
k
2
+
1
]
)
2
f[k1][j-1]+(a[i]-a[k1+1])^2<f[k2][j-1]+(a[i]-a[k2+1])^2
f[k1][j−1]+(a[i]−a[k1+1])2<f[k2][j−1]+(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
<
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<f[k2][j-1]+a[i]^2-2*a[i]*a[k2+1]+a[k2+1]^2
f[k1][j−1]+a[i]2−2∗a[i]∗a[k1+1]+a[k1+1]2<f[k2][j−1]+a[i]2−2∗a[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
<
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<f[k2][j-1]-2*a[i]*a[k2+1]+a[k2+1]^2
f[k1][j−1]−2∗a[i]∗a[k1+1]+a[k1+1]2<f[k2][j−1]−2∗a[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
<
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<2*a[i]*(a[k1+1]-a[k2+1])
f[k1][j−1]+a[k1+1]2−f[k2][j−1]−a[k2+1]2<2∗a[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
]
<
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]}<2*a[i]
a[k1+1]−a[k2+1](f[k1][j−1]+a[k1+1]2)−(f[k2][j−1]+a[k2+1]2)<2∗a[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)的点。
因此,若两个点之间的斜率满足
<
2
∗
a
[
i
]
<2*a[i]
<2∗a[i]时,那么前一个点在这一次转移中是没有作用的(劣与后一个点),当然,否则后一个点是没有用的。
关于式子的转换大概就这么多。
关键在于这有什么用。
如果斜率满足单调性的话,一开始我们的想法就成立了。
将相邻两个待转移的点直接连上线。
于是我们试图求这些待转移的点的凸包。
我们试图维护下凸壳。
如果假设存在(i,j)与(j,k)。如果(i,j,k)构成上凸,那么就有
x
l
(
i
,
j
)
>
x
l
(
j
,
k
)
(
x
l
表
示
斜
率
)
xl(i,j)>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
)
<
2
∗
a
[
i
]
xl(i,j)<2*a[i]
xl(i,j)<2∗a[i]时,i决策点要劣于j,当
x
l
(
i
,
j
)
>
=
2
∗
a
[
i
]
xl(i,j)>=2*a[i]
xl(i,j)>=2∗a[i]时,i决策点要优于(或等于)j。所以我们只需要取到满足
x
l
(
i
−
1
,
i
)
<
2
∗
a
[
i
]
xl(i-1,i)<2*a[i]
xl(i−1,i)<2∗a[i]且
x
l
(
i
,
i
+
1
)
>
=
2
∗
a
[
i
]
xl(i,i+1)>=2*a[i]
xl(i,i+1)>=2∗a[i]时,i就是最小的决策点。
所以我们可以二分解决最小决策点的找寻问题。
这样的时间复杂度是: O ( n ∗ l o g ( n ) ∗ m ) O(n*log(n)*m) O(n∗log(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]);
}