1. 程式人生 > >【bzoj3675】[Apio2014]序列分割 斜率優化dp

【bzoj3675】[Apio2014]序列分割 斜率優化dp

sof turn col 兩個 led 輸入 分數 包含 class

原文地址:http://www.cnblogs.com/GXZlegend/p/6835179.html


題目描述

小H最近迷上了一個分隔序列的遊戲。在這個遊戲裏,小H需要將一個長度為n的非負整數序列分割成k+1個非空的子序列。為了得到k+1個子序列,小H需要重復k次以下的步驟: 1.小H首先選擇一個長度超過1的序列(一開始小H只有一個長度為n的序列——也就是一開始得到的整個序列); 2.選擇一個位置,並通過這個位置將這個序列分割成連續的兩個非空的新序列。 每次進行上述步驟之後,小H將會得到一定的分數。這個分數為兩個新序列中元素和的乘積。小H希望選擇一種最佳的分割方式,使得k輪之後,小H的總得分最大。

輸入

輸入第一行包含兩個整數n,k(k+1≤n)。

第二行包含n個非負整數a1,a2,...,an(0≤ai≤10^4),表示一開始小H得到的序列。

輸出

輸出第一行包含一個整數,為小H可以得到的最大分數。

樣例輸入

7 3
4 1 3 4 0 2 3

樣例輸出

108


題解

斜率優化dp

首先可以證得分割的順序對答案沒有影響。

設連續的3部分之和分別為a、b、c,則先分割ab再分割bc的分數為a*(b+c)+b*c=a*b+a*c+b*c,先分割bc再分割ab的分數為(a+b)*c+a*b=a*b+a*c+b*c,它們完全相同。

這樣我們就可以從左到右按順序依次分割。

設f[i][p]表示前i個數分割p次的最大分數,那麽有f[i][p]=f[j][p-1]+(sum[i]-sum[j])*sum[j]

這樣時間復雜度為O(n^2*p),會TLE,需要優化。

將dp方程變形,得sum[j]*sum[j]-f[j][p-1]=sum[i]*sum[j]-f[i][p],

這樣可以斜率優化來解決,此時y=sum[j]*sum[j]-f[j][p-1],k=sum[i],x=sum[j],均為正數(其實是故意這樣移項的)。

然後要求的是f[i][p]的最大值,即-f[i][p]的最小值,所以應該維護一個上凸包。

另外需要開long long導致MLE,所以需要滾動數組。

#include <cstdio>
#define y(i , d) (sum[i] * sum[i] - f[i][d])
typedef long long ll;
ll sum[100010] , f[100010][2];
int q[100010];
int main()
{
	int n , k , i , j , l , r , d = 0;
	scanf("%d%d" , &n , &k);
	for(i = 1 ; i <= n ; i ++ ) scanf("%lld" , &sum[i]) , sum[i] += sum[i - 1];
	for(j = 1 ; j <= k ; j ++ )
	{
		l = r = 0 , d ^= 1;
		for(i = 1 ; i <= n ; i ++ )
		{
			while(l < r && y(q[l + 1] , d ^ 1) - y(q[l] , d ^ 1) <= sum[i] * (sum[q[l + 1]] - sum[q[l]])) l ++ ;
			f[i][d] = f[q[l]][d ^ 1] + (sum[i] - sum[q[l]]) * sum[q[l]];
			while(l < r && (y(i , d ^ 1) - y(q[r] , d ^ 1)) * (sum[q[r]] - sum[q[r - 1]]) <= (y(q[r] , d ^ 1) - y(q[r - 1] , d ^ 1)) * (sum[i] - sum[q[r]])) r -- ;
			q[++r] = i;
		}
	}
	printf("%lld\n" , f[n][k & 1]);
	return 0;
}

【bzoj3675】[Apio2014]序列分割 斜率優化dp