P7962-[NOIP2021]方差 (动态规划)

题目链接

前置知识

方差:衡量一组数据的波动程度大小

解题思路

直接根据方差的定义打出代码,可以拿到32pts的成绩

32pts代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
using namespace std;
int a[10010],n;
double x,ans;
double calc(){
	double sum = 0;
	for(int i=1;i<=n;i++){
		sum += 1.0*(a[i]-x)*(a[i]-x);
	}
	return sum;
}
int main(){
	//freopen("variance.in","r",stdin);
	//freopen("variance.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		x += a[i];
	}
	x /= 1.0*n;
	ans = calc();
	
	//cout<<x<<"  "<<double(ans)<<endl;
	
	bool no_ok = false;
	do{
		no_ok = 0;
		for(int i=n-1;i>=2;i--){
			if(abs((a[i-1]+a[i+1]-a[i])-x)<abs(a[i]-x)){
			//减小数据波动程度
				x += ((a[i-1]+a[i+1]-a[i])-a[i])*1.0/n;
				a[i] = (a[i-1]+a[i+1]-a[i]);
				ans = calc();
				
				//cout<<x<<" "<<double(ans)<<endl;
				//for(int k=1;k<=n;k++){
				//	cout<<double(a[k])<<" ";
				//}
				//cout<<endl;
				
				no_ok = 1;
			}
		}
	}while(no_ok);
	ans *= n;
	cout<<ans;
	//fclose(stdin);
	//fclose(stdout);
	return 0; 
}

好,扯远了,现在进入正题

一、式子

看到题目的式子:
D = 1 n ∑ i = 1 n ( a i − a ˉ ) 2 \frac {1}{n} \sum_{i=1}^{n} (a_{i}-\bar {a})^{2} n1i=1n(aiaˉ)2,其中 a ˉ = 1 n ∑ i = 1 n a i \bar a = \frac{1}{n} \sum_{i = 1}^{n} a_i aˉ=n1i=1nai
要求输出方差的最小值的平方,即 D 2 D^{2} D2
设最终答案为ans,我们得到这样一条式子:
a n s = n 2 ⋅ 1 n ∑ i = 1 n ( a i − a ˉ ) 2 ans = n^2 \cdot \frac {1}{n} \sum_{i=1}^{n} (a_{i}-\bar {a})^{2} ans=n2n1i=1n(aiaˉ)2
化简一下:
a n s = n ∑ i = 1 n ( a i − a ˉ ) 2 ans = n \sum_{i=1}^{n} (a_{i}-\bar {a})^{2} ans=ni=1n(aiaˉ)2

a n s = n ∑ i = 1 n a i 2 − 2 n ∑ i = 1 n a i a ˉ + n 2 a ˉ 2 ans = n \sum_{i=1}^{n} a_{i}^2-2n\sum_{i=1}^{n} a_{i} \bar {a} + n^2\bar a^2 ans=ni=1nai22ni=1naiaˉ+n2aˉ2

a n s = n ∑ i = 1 n a i 2 − 2 n ∑ i = 1 n a i a ˉ + n ⋅ n a ˉ ⋅ a ˉ ans = n \sum_{i=1}^{n} a_{i}^2-2n\sum_{i=1}^{n} a_{i} \bar {a} + n\cdot n\bar a\cdot \bar a ans=ni=1nai22ni=1naiaˉ+nnaˉaˉ

a n s = n ∑ i = 1 n a i 2 − 2 n ∑ i = 1 n a i a ˉ + n ∑ i = 1 n a i a ˉ ans = n \sum_{i=1}^{n} a_{i}^2-2n\sum_{i=1}^{n} a_{i} \bar {a} + n \sum_{i=1}^{n} a_{i} \bar a ans=ni=1nai22ni=1naiaˉ+ni=1naiaˉ

a n s = n ∑ i = 1 n a i 2 − n ∑ i = 1 n a i a ˉ ans = n \sum_{i=1}^{n} a_{i}^2- n \sum_{i=1}^{n} a_{i} \bar a ans=ni=1nai2ni=1naiaˉ

a n s = n ∑ i = 1 n a i 2 − ( ∑ i = 1 n a i ) 2 ans = n \sum_{i=1}^{n} a_{i}^2- ( \sum_{i=1}^{n} a_{i} )^2 ans=ni=1nai2(i=1nai)2
最终化简结果如上。

二、操作

任意选择一个正整数 1 < i < n 1 < i < n 1<i<n,将 a i a_i ai 变为 a i − 1 + a i + 1 − a i a_{i - 1} + a_{i + 1} - a_i ai1+ai+1ai
画个图观察一下。
下图为a数组。
原a[1,2,3,4] = 1,2,4,6(样例数据)
a数组变化
下面看看差分d数组的变化。
原d[1,2,3] = 1,2,2
差分数组变化
肉眼观察,可以发现,当i=2,a[2] = a[1]+a[3]-a[2] = 3
而d[1]与d[2]的值交换了。
大胆猜测一下,当操作a[i]时,d[i-1]的值与d[i]的值交换。
举多几个例子,可以验证这个猜想是正确的
也就是说我们可以把差分数组任意重排!!!

三、数据分析

观察题目给的样例
在这里插入图片描述
我们再看样例分析
对于 ( a 1 , a 2 , a 3 , a 4 ) = ( 1 , 2 , 4 , 6 ) (a_1, a_2, a_3, a_4) = (1, 2, 4, 6) (a1,a2,a3,a4)=(1,2,4,6),第一次操作得到的数列有 ( 1 , 3 , 4 , 6 ) (1, 3, 4, 6) (1,3,4,6),第二次操作得到的新的数列有 ( 1 , 3 , 5 , 6 ) (1, 3, 5, 6) (1,3,5,6)。之后无法得到新的数列。

对于 ( a 1 , a 2 , a 3 , a 4 ) = ( 1 , 2 , 4 , 6 ) (a_1, a_2, a_3, a_4) = (1, 2, 4, 6) (a1,a2,a3,a4)=(1,2,4,6),平均值为 13 4 \frac{13}{4} 413,方差为 1 4 ( ( 1 − 13 4 ) 2 + ( 2 − 13 4 ) 2 + ( 4 − 13 4 ) 2 + ( 6 − 13 4 ) 2 ) = 59 16 \frac{1}{4}({(1 - \frac{13}{4})}^2 + {(2 - \frac{13}{4})}^2 + {(4 - \frac{13}{4})}^2 + {(6 - \frac{13}{4})}^2) = \frac{59}{16} 41((1413)2+(2413)2+(4413)2+(6413)2)=1659

对于 ( a 1 , a 2 , a 3 , a 4 ) = ( 1 , 3 , 4 , 6 ) (a_1, a_2, a_3, a_4) = (1, 3, 4, 6) (a1,a2,a3,a4)=(1,3,4,6),平均值为 7 2 \frac{7}{2} 27,方差为 1 4 ( ( 1 − 7 2 ) 2 + ( 3 − 7 2 ) 2 + ( 4 − 7 2 ) 2 + ( 6 − 7 2 ) 2 ) = 13 4 \frac{1}{4} ({(1 - \frac{7}{2})}^2 + {(3 - \frac{7}{2})}^2 + {(4 - \frac{7}{2})}^2 + {(6 - \frac{7}{2})}^2) = \frac{13}{4} 41((127)2+(327)2+(427)2+(627)2)=413

对于 ( a 1 , a 2 , a 3 , a 4 ) = ( 1 , 3 , 5 , 6 ) (a_1, a_2, a_3, a_4) = (1, 3, 5, 6) (a1,a2,a3,a4)=(1,3,5,6),平均值为 15 4 \frac{15}{4} 415,方差为 1 4 ( ( 1 − 15 4 ) 2 + ( 3 − 15 4 ) 2 + ( 5 − 15 4 ) 2 + ( 6 − 15 4 ) 2 ) = 59 16 \frac{1}{4} ({(1 - \frac{15}{4})}^2 + {(3 - \frac{15}{4})}^2 + {(5 - \frac{15}{4})}^2 + {(6 - \frac{15}{4})}^2) = \frac{59}{16} 41((1415)2+(3415)2+(5415)2+(6415)2)=1659

由此得知,方差有最小值时的数组为(1,3,4,6)
我们根据分析操作时的结论,算出此时的差分d数组

咦?好像中间小两边大(满足单谷性质)诶?
再次举多几个例子,可以发现最优情况下,差分数组都满足单谷性质,这也很容易理解,就不细讲了(严谨证明的话可以用邻值调整法)。

四、总结

到这里,正确思路已经大致出现了。
将差分数组算出后,进行一个排序,然后依次考虑每一项,要么插在当前最左边,要么插在当前最右边。
d p [ i ] [ j ] dp[i][j] dp[i][j] 表示考虑了前 i 个数,当前 ∑ a i = j \sum a_i = j ai=j 的情况下,最小的 ∑ a i 2 \sum a_i^2 ai2 是多少。
但是空间限制,我们需要压掉第一维,用滚动数组实现

s s s数组是记录排序后的差分数组前缀和
d p 过 程 dp过程 dp
放在左边
插入后的的数组前缀和全部都要加上 d [ i ] d[i] d[i]
之前已经放入的数据的和就加上了 i ∗ d [ i ] i*d[i] id[i]
那 要 加 上 的 平 方 和 呢 ? 那要加上的平方和呢?
∑ j = 1 i ( a j + d i ) 2 − ∑ j = 1 i a j 2 \sum_{j=1}^i (a_j + d_i) ^ 2 - \sum_{j=1}^i a_j ^ 2 j=1i(aj+di)2j=1iaj2
= ∑ j = 1 i ( a j 2 + 2 ⋅ a j ⋅ d i + d i 2 ) − ∑ j = 1 i a j 2 = \sum_{j=1}^i (a_j^2 + 2 \cdot a_j \cdot d_i + d_i ^ 2) - \sum_{j=1}^i a_j^2 =j=1i(aj2+2ajdi+di2)j=1iaj2
= ∑ j = 1 i ( 2 ⋅ a j ⋅ d i + d i 2 ) = \sum_{j=1}^i (2 \cdot a_j \cdot d_i + d_i^2) =j=1i(2ajdi+di2)
= 2 ⋅ d i ⋅ ∑ j = 1 i a j + i ⋅ d i 2 = 2 \cdot d_i \cdot \sum_{j=1}^i a_j + i \cdot d_i ^ 2 =2dij=1iaj+idi2
所以只要实时记录一下 ∑ j = 1 i a j \sum_{j=1}^i a_j j=1iaj 就OK了。

d p [ j + i ∗ d [ i ] ] = m i n ( d p [ j + i ∗ d [ i ] ] , d p [ j ] + 2 ∗ d [ i ] ∗ j + i ∗ d [ i ] ∗ d [ i ] ) dp[j+i*d[i]] = min(dp[j+i*d[i]],dp[j]+2*d[i]*j+i*d[i]*d[i]) dp[j+id[i]]=min(dp[j+id[i]],dp[j]+2d[i]j+id[i]d[i])

放在右边
总和要加上已经进入数组的前缀和,
平方和也只需加上新插入的平方,很好理解
d p [ j + s [ i ] ] = m i n ( d p [ j + s [ i ] ] , d p [ j ] + s [ i ] ∗ s [ i ] ) dp[j+s[i]] = min(dp[j+s[i]],dp[j]+s[i]*s[i]) dp[j+s[i]]=min(dp[j+s[i]],dp[j]+s[i]s[i])

AC代码

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
using namespace std;
int a[10010],d[10010],s[10010],n,ss;
long long dp[500100],ans,lim;
const long long inf = 1e12;
long long minn(long long a,long long b){
	return a<=b ? a : b;
}
int main(){
//	freopen("variance.in","r",stdin);
//	freopen("variance.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i=2;i<=n;i++){
		d[i-1] = a[i]-a[i-1];
	}
	sort(d+1,d+n);
	for(int i=1;i<n;i++){
		s[i] = s[i-1] + d[i];
	}
	lim = n*a[n];
	for(int i=0;i<=lim;i++){//初始化
		dp[i] = inf;
	}
	dp[0] = 0;
	for(int i=1;i<n;i++){
		if(!s[i]) continue;
		for(int j=lim;j>=0;j--){
			if(dp[j]==inf) continue;
			dp[j+s[i]] = minn(dp[j+s[i]],dp[j]+s[i]*s[i]);//放在右边
			dp[j+i*d[i]] = minn(dp[j+i*d[i]],dp[j]+2*d[i]*j+i*d[i]*d[i]);//放在左边
			dp[j] = inf;
		}
	}
	ans = inf;
	for(long long i=0;i<=lim;i++){
		if(dp[i]==inf) continue;
        ans=minn(ans,n*dp[i]-i*i);
	}
	printf("%lld",ans);
//	fclose(stdin);
//	fclose(stdout);
	return 0; 
}

作者的第一篇题解,若有不足,欢迎提出建议。
版权说明:本文为博主原创文章,转载请附上本文链接(https://blog.csdn.net/smart_CZH/article/details/124759338)

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值