打印文章

https://loj.ac/problem/10191

题目描述

  给出\(N\)个单词,每个单词有个非负权值\(C_i\),现要将它们分成连续的若干段,每段的代价为此段单词的权值和的平方,还要加一个常数M,即\((\sum C_i)^2+M\)。现在想求出一种最优方案,使得总费用之和最小。

思路

  连续的若干段单词,我们显然可以列出朴素的状态转移方程

\[f[i]=min\{f[j]+(sum[i]-sum[j])^2+M\} \]

  很容易化为一次函数的式子

\[f[j]+sum[j]^2=2*sum[i]*sum[j]-sum[i]^2-M+f[i] \]

  显然这就是要截距最小,而且具有单调性,直接维护下凸壳即可。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=5e5+10;

ll read()
{
	ll res=0,w=1;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){res=(res<<3)+(res<<1)+(ch^48);ch=getchar();}
	return res*w;
}
void write(ll x)
{
	if(x>9)write(x/10);
	putchar(x%10+'0');
}
void writeln(ll x)
{
	write(x);
	putchar('\n');
}

ll s[N],q[N],f[N];
ll X(ll x){return s[x];}
ll Y(ll x){return f[x]+s[x]*s[x];}
ll K(ll x){return 2*s[x];}
int main()
{
	ll n,m;
	while(~scanf("%lld",&n))
	{
		m=read();
		for(ll i=1;i<=n;i++)
			s[i]=s[i-1]+read();
		ll l=0,r=0;
		for(ll i=1;i<=n;i++)
		{
			while(l<r&&(Y(q[l+1])-Y(q[l]))<=K(i)*(X(q[l+1])-X(q[l])))l++;
			f[i]=f[q[l]]+(s[i]-s[q[l]])*(s[i]-s[q[l]])+m;
			while(l<r&&(Y(q[r])-Y(q[r-1]))*(X(i)-X(q[r]))>=(Y(i)-Y(q[r]))*(X(q[r])-X(q[r-1])))r--;
			q[++r]=i;
		}
		writeln(f[n]);
	}
}
posted @ 2019-11-13 21:56  fbz  阅读(248)  评论(0编辑  收藏  举报