背景:

我连这都不会…

题目传送门:

(这是一道例题)http://acm.hdu.edu.cn/showproblem.php?pid=3507

题意:

给定n,mn,m,接下来有nn个数aia_i,表示每一个单词的权值。现在你需要将这些单词分行,每一行的权值的计算公式是:(i=1kai)2+m(\sum_{i=1}^{k}a_i)^2+m,现在求最小的权值和。

思路:

显然我们能想到常规做法。
fif_i表示从第11个单词到第ii个单词的权值和的最小值。
sumk=i=1kaisum_k=\sum_{i=1}^{k}a_i
显然得到转移方程:fi=minj=1i{fj+(sumisumj)2}f_i=\min_{j=1}^{i}\{f_j+(sum_{i}-sum_j)^2\}
时间复杂度:Θ(n2)\Theta(n^2)

可是我们过不了怎么办?
考虑iij,kj,k转移过来的情况,且从jj转移更优,j>kj>k
可以得到不等式:
fj+(sumisumj)2<fk+(sumisumk)2f_j+(sum_i-sum_j)^2<f_k+(sum_i-sum_k)^2

展开括号,得:
fj+sumi2+sumj22sumisumj<fk+sumi2+sumk22sumisumkf_j+sum_i^2+sum_j^2-2sum_isum_j<f_k+sum_i^2+sum_k^2-2sum_isum_k

消去同类项,得:
fj+sumj22sumisumj<fk+sumk22sumisumkf_j+sum_j^2-2sum_isum_j<f_k+sum_k^2-2sum_isum_k

再移项,得:
fjfk+sumj2sumk2<+2sumisumj2sumisumkf_j-f_k+sum_j^2-sum_k^2<+2sum_isum_j-2sum_isum_k

提取同类项,得:
fjfk+sumj2sumk2<2sumi(sumjsumk)f_j-f_k+sum_j^2-sum_k^2<2sum_i(sum_j-sum_k)

再移项,得:
fjfk+sumj2sumk2sumjsumk<2sumi+1\frac{f_j-f_k+sum_j^2-sum_k^2}{sum_j-sum_k}<2sum_{i+1}

Ti=fi+sumi2T_i=f_i+sum_i^2,得:
TjTksumjsumk<2sumi+1\frac{T_j-T_k}{sum_j-sum_k}<2sum_{i+1}

结论:也就是说若存在j>kj>kTjTksumjsumk<2sumi\frac{T_j-T_k}{sum_j-sum_k}<2sum_i,那么从jj转移更优。

ps:ps:联想一下数学课本中的k=y2y1x2x1k=\frac{y_2-y_1}{x_2-x_1},发现左边的式子好像斜率啊。斜率优化就是这么来的。

那么有什么用呢?
斜率优化学习笔记
因为TT是由ffsumsum得到的,sumsum是固定的,所以我们求完了ff,就可以得到TT
如图,从我们的斜率推导中我们可以以sumsum作为xx轴,TT作为yy轴建立坐标系,对应的点代表ff的值。
我们在求解fnowf_{now}时,假设可选i,j,ki,j,k三个点转移,且如上图所示。
由于kkj>kjik_{kj}>k_{ji},所以TjTksumjsumk>TiTjsumisumj\frac{T_j-T_k}{sum_j-sum_k}>\frac{T_i-T_j}{sum_i-sum_j}
由上面的结论可知两边的式子要与2sumnow2sum_{now}比较。
就存在三种可能:
[1]:[1]:
TjTksumjsumk>TiTjsumisumj>2sumnow\frac{T_j-T_k}{sum_j-sum_k}>\frac{T_i-T_j}{sum_i-sum_j}>2sum_{now}

由上面的结论可知此时jjii优,但kkjj优,所以选择kk转移。

[2]:[2]:
TjTksumjsumk>2sumnow>TiTjsumisumj\frac{T_j-T_k}{sum_j-sum_k}>2sum_{now}>\frac{T_i-T_j}{sum_i-sum_j}
由上面的结论可知此时kkjj优,iijj优,所以选择iikk转移。

[3]:[3]:
2sumnow>TjTksumjsumk>TiTjsumisumj2sum_{now}>\frac{T_j-T_k}{sum_j-sum_k}>\frac{T_i-T_j}{sum_i-sum_j}
由上面的结论可知此时iijj优,jjkk优,所以选择ii转移。


综上所述,我们一定不会选择从jj转移。


我们维护一个这样的类似凸包且斜率单调递增的东西:
斜率优化学习笔记
假设kji>2sumnowk_{ji}>2sum_{now}kkj<2sumnowk_{kj}<2sum_{now},我们可以从上面的结论得出jj点比所有比kk小的点都优,比所有比ii大的也优。所以我们二分查找斜率比2sumnow2sum_{now}小的编号最大的点,就是最优的转移点。由于sumsum也有单调性,我们直接维护一个单调队列就可以了。
维护时,若队列前面的点不满足结论,则删掉;若后面的点不满足斜率单调递增,则删掉。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
	int n,m;
	LL a[1000010],sum[1000010],f[1000010];
	int que[1000010];
LL calc1(int x,int y)
{
	return sum[y]-sum[x];
}
LL calc2(int x,int y)
{
	return (f[y]+sum[y]*sum[y])-(f[x]+sum[x]*sum[x]);
}
int main()
{
	while(scanf("%d %d",&n,&m)!=EOF)
	{
		f[0]=sum[0]=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);
			sum[i]=sum[i-1]+a[i];
		}
		int head=1,tail=1;
		que[1]=0;
		for(int i=1;i<=n;i++)
		{
			while(head<tail&&calc2(que[head],que[head+1])<=(LL)2*sum[i]*calc1(que[head],que[head+1])) head++;
			/*原来形如calc2(x,y)/calc1(x,y)<=2*sum[i],移项(使得没有小数计算),得到上面的式子*/
			f[i]=f[que[head]]+calc1(que[head],i)*calc1(que[head],i)+m;
			while(head<tail&&calc2(que[tail],i)*calc1(que[tail-1],que[tail])<=calc2(que[tail-1],que[tail])*calc1(que[tail],i)) tail--;
			/*原来形如calc2(x1,y1)/calc2(x1,y1)<=calc2(x2,y2)*calc1(x2,y2)/,移项(使得没有小数计算),得到上面的式子*/ 
			que[++tail]=i;
		}
		printf("%lld\n",f[n]);
	}
}

相关文章: