P4072 [SDOI2016]征途(斜率优化 dp + 细节)

在这里插入图片描述


设序列 a 1 , a 2 , a 3 , a 4 , . . . , a n a_1,a_2,a_3,a_4,...,a_n a1,a2,a3,a4,...,an 的平均值为 x x x,权值和为 t o t tot tot,方差 S = ∑ i = 1 n ( a i − x ) 2 n S = \displaystyle\frac{\displaystyle\sum_{i = 1}^n(a_i -x)^2}{n} S=ni=1n(aix)2
乘上 n 2 n^2 n2 得到: S = ∑ i = 1 n ( m ∗ a i − t o t ) 2 n S = \displaystyle\frac{\displaystyle\sum_{i = 1}^n(m*a_i -tot)^2}{n} S=ni=1n(maitot)2

统计分子部分,最后再除以 n。

题目转化过来就是将 n 个数分成 m 段,使得最后 S S S 尽可能小。
每一段对 S S S 的分子部分的贡献不难计算,考虑 dp,dp[i][j] 表示前 i 个数分成 j 段对 S 的最小贡献,答案显然为 dp[n][m]

转移方程: d p [ i ] [ t ] = min ⁡ j = 1 i ( d p [ j ] [ t − 1 ] + ( m ∗ ( s u m [ i ] − s u m [ j ] ) − t o t ) 2 ) dp[i][t] = \displaystyle\min_{j = 1}^i(dp[j][t - 1] + (m * (sum[i] - sum[j]) - tot)^2) dp[i][t]=j=1mini(dp[j][t1]+(m(sum[i]sum[j])tot)2)

注意初值,对所有的 i < j , d p [ i ] [ j ] = i n f i <j,dp[i][j] = inf i<j,dp[i][j]=inf

直接做的复杂度是 n 2 m n^2m n2m,考虑优化,将式子展开可以得到:

d p [ i ] [ t ] = min ⁡ j = 1 i ( d p [ j ] [ t − 1 ] + m 2 ∗ s u m [ i ] 2 + m 2 ∗ s u m [ j ] 2 − 2 m 2 ∗ s u m [ i ] ∗ s u m [ j ] + t o t 2 − 2 m ∗ t o t ∗ s u m [ i ] + 2 ∗ m ∗ t o t ∗ s u m [ j ] ) dp[i][t] = \displaystyle\min_{j = 1}^i(dp[j][t - 1] + m^2*sum[i]^2+m^2*sum[j]^2-2m^2*sum[i]*sum[j]+tot^2-2m*tot*sum[i]+2*m*tot*sum[j]) dp[i][t]=j=1mini(dp[j][t1]+m2sum[i]2+m2sum[j]22m2sum[i]sum[j]+tot22mtotsum[i]+2mtotsum[j])

出现了 2 m 2 ∗ s u m [ i ] ∗ s u m [ j ] 2m^2*sum[i]*sum[j] 2m2sum[i]sum[j],考虑斜率优化,斜率为 2 m ∗ s u m [ i ] 2m*sum[i] 2msum[i] (移项后的斜率),斜率单调递增,要求 d p [ i ] [ t ] dp[i][t] dp[i][t] 最小值,维护下凸包。

j > k j > k j>k,在 j j j 点比在 k k k 点转移更优,则满足 :
d p [ j ] [ t − 1 ] + m 2 ∗ s u m [ i ] 2 + m 2 ∗ s u m [ j ] 2 − 2 m 2 ∗ s u m [ i ] ∗ s u m [ j ] + t o t 2 − 2 m ∗ t o t ∗ s u m [ i ] + 2 ∗ m ∗ t o t ∗ s u m [ j ] dp[j][t - 1] + m^2*sum[i]^2+m^2*sum[j]^2-2m^2*sum[i]*sum[j]+tot^2-2m*tot*sum[i]+2*m*tot*sum[j] dp[j][t1]+m2sum[i]2+m2sum[j]22m2sum[i]sum[j]+tot22mtotsum[i]+2mtotsum[j] < < <

d p [ k ] [ t − 1 ] + m 2 ∗ s u m [ i ] 2 + m 2 ∗ s u m [ k ] 2 − 2 m 2 ∗ s u m [ i ] ∗ s u m [ k ] + t o t 2 − 2 m ∗ t o t ∗ s u m [ i ] + 2 ∗ m ∗ t o t ∗ s u m [ k ] dp[k][t - 1] + m^2*sum[i]^2+m^2*sum[k]^2-2m^2*sum[i]*sum[k]+tot^2 -2m*tot*sum[i]+2*m*tot*sum[k] dp[k][t1]+m2sum[i]2+m2sum[k]22m2sum[i]sum[k]+tot22mtotsum[i]+2mtotsum[k]

式子非常长,后面一段就是把 j 换成 k,消掉相同的并进行移项化简,最后可以得到:

c [ i ] [ t ] = d p [ i ] [ t ] + m 2 ∗ s u m [ i ] 2 + 2 m ∗ t o t ∗ s u m [ i ] c[i][t] = dp[i][t] + m^2*sum[i]^2 + 2m*tot*sum[i] c[i][t]=dp[i][t]+m2sum[i]2+2mtotsum[i]

代入得: c [ j ] [ t − 1 ] − c [ k ] [ t − 1 ] s u m [ j ] − s u m [ t ] < 2 m ∗ s u m [ i ] \displaystyle\frac{c[j][t - 1] - c[k][t - 1]}{sum[j] - sum[t]} < 2m*sum[i] sum[j]sum[t]c[j][t1]c[k][t1]<2msum[i]

用单调队列维护 c [ j ] [ t − 1 ] − c [ k ] [ t − 1 ] s u m [ j ] − s u m [ t ] \displaystyle\frac{c[j][t - 1] - c[k][t - 1]}{sum[j] - sum[t]} sum[j]sum[t]c[j][t1]c[k][t1] 单增,根据决策单调性寻找最优转移点,复杂度降为 O ( n ∗ m ) O(n*m) O(nm)

对第二维可以进行滚动,最后答案要记得除以 m。


代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e3 + 10;
typedef long long ll;
const ll inf = 1e17;
int n,N;
ll sum[maxn],dp[maxn],tp[maxn],c[maxn],m[5],tot;
int q[maxn],front,rear;
ll calc(int x,int y) {
    
    
	ll tmp = m[1] * (sum[x] - sum[y]) - tot;
	return tp[y] + tmp * tmp;
}
int main() {
    
    
	scanf("%d%d",&n,&m[1]);
	for (int i = 2; i <= 4; i++)
		m[i] = 1ll * m[i - 1] * m[1];
	for (int i = 1,x; i <= n; i++)
		scanf("%d",&sum[i]);
	for (int i = 1; i <= n; i++) 
		sum[i] += sum[i - 1];
	tot = sum[n];
	for (int j = 1; j <= n; j++)
		tp[j] = dp[j] = inf;
	for (int t = 1; t <= m[1]; t++) {
    
    
		front = rear = 0;
		q[++rear] = t - 1;
		for (int i = t; i <= n; i++) {
    
    
			while (front + 1 < rear && calc(i,q[front + 1]) >= calc(i,q[front + 2]))
				front++;
			dp[i] = calc(i,q[front + 1]);
			while (front + 1 < rear && (c[i] - c[q[rear]]) * (sum[q[rear]] - sum[q[rear - 1]]) 
				<= (c[q[rear]] - c[q[rear - 1]]) * (sum[i] - sum[q[rear]]))
				rear--;
			q[++rear] = i;
		}
		for (int i = 0; i <= n; i++) {
    
    
			tp[i] = dp[i];
			c[i] = tp[i] + m[2] * sum[i] * sum[i] + 2 * m[1] * tot * sum[i];
			dp[i] = inf;
		}
	}	
	printf("%lld\n",tp[n] / m[1]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41997978/article/details/104734709