题目大意:将n个数分成若干组,并且每组的数在原数组中应是连续的,每组会产生的代价为sum(i)-sum(j)+i-j-1-m,m为已知的常数。求最小代价。
题目分析:定义dp(i)表示将前 i 个元素分好组后产生的最小代价,状态转移方程很显然了:
dp(i)=min(dp(j)+[sum(i)-sum(j)+i-j-1-m)]^2)。另f(i)=sum(i)+i,并且另g(i)=f(i)-1-m,则dp(i)可整理成dp(i)=min(dp(j)+sum(j)^2-2*g(i)*sum(j))+g(i)。很显然需要斜率优化。
代码如下:
# include<iostream> # include<cstdio> # include<cstring> # include<algorithm> using namespace std; # define LL long long const int N=50005; int n,m; int q[N]; LL a[N]; LL dp[N]; LL sum[N]; void read(LL &x) { char ch=' '; while(ch<'0'||ch>'9') ch=getchar(); x=0; while(ch>='0'&&ch<='9'){ x=x*10+ch-'0'; ch=getchar(); } } void init() { sum[0]=0; for(int i=1;i<=n;++i){ read(a[i]); sum[i]=a[i]+sum[i-1]; } for(int i=1;i<=n;++i) sum[i]+=i; } LL getSon(int k,int j) { return dp[j]-dp[k]+(sum[j]+sum[k])*(sum[j]-sum[k]); } LL getMother(int k,int j) { return 2*(sum[j]-sum[k]); } double getK(int i,int j) { return (double)getSon(i,j)/(double)getMother(i,j); } LL toDp(int j,int i) { return dp[j]+(sum[i]-sum[j]-m-1)*(sum[i]-sum[j]-m-1); } LL solve() { int head=0,tail=-1; q[++tail]=0; dp[0]=0; for(int i=1;i<=n;++i){ while(head+1<=tail&&getK(q[head],q[head+1])<=sum[i]-m-1) ++head; dp[i]=toDp(q[head],i); while(head+1<=tail&&getK(q[tail-1],q[tail])>getK(q[tail],i)) --tail; q[++tail]=i; } return dp[n]; } int main() { while(~scanf("%d%d",&n,&m)) { init(); printf("%lld ",solve()); } return 0; }