Problem:

题目描述

给定长度为 \(n\) 的非严格递增正整数数列 \(1 \le a_1 \le a_2 \le \cdots \le a_n\)。每次可以进行的操作是:任意选择一个正整数 \(1 < i < n\),将 \(a_i\) 变为 \(a_{i - 1} + a_{i + 1} - a_i\)。求在若干次操作之后,该数列的方差最小值是多少。请输出最小值乘以 \(n^2\)

其中方差的定义为:数列中每个数与平均值的差的平方的平均值。更形式化地说,方差的定义为 \(D = \frac{1}{n} \sum_{i = 1}^{n} {(a_i - \bar a)}^2\),其中 \(\bar a = \frac{1}{n} \sum_{i = 1}^{n} a_i\)。

输入格式

输入的第一行包含一个正整数 \(n\),保证 \(n \le {10}^4\)。

输入的第二行有 \(n\) 个正整数,其中第 \(i\) 个数字表示 \(a_i\) 的值。数据保证 \(1 \le a_1 \le a_2 \le \cdots \le a_n\)。

输出格式

输出仅一行,包含一个非负整数,表示你所求的方差的最小值的 \(n^2\)

样例 #1

样例输入 #1

4
1 2 4 6

样例输出 #1

52

样例 #2

样例输入 #2

见附件中的 variance/variance2.in

样例输出 #2

见附件中的 variance/variance2.ans

样例 #3

样例输入 #3

见附件中的 variance/variance3.in

样例输出 #3

见附件中的 variance/variance3.ans

样例 #4

样例输入 #4

见附件中的 variance/variance4.in

样例输出 #4

见附件中的 variance/variance4.ans

提示

【样例解释 #1】

对于 \((a_1, a_2, a_3, a_4) = (1, 2, 4, 6)\),第一次操作得到的数列有 \((1, 3, 4, 6)\),第二次操作得到的新的数列有 \((1, 3, 5, 6)\)。之后无法得到新的数列。

对于 \((a_1, a_2, a_3, a_4) = (1, 2, 4, 6)\),平均值为 \(\frac{13}{4}\),方差为 \(\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}\)。

对于 \((a_1, a_2, a_3, a_4) = (1, 3, 4, 6)\),平均值为 \(\frac{7}{2}\),方差为 \(\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}\)。

对于 \((a_1, a_2, a_3, a_4) = (1, 3, 5, 6)\),平均值为 \(\frac{15}{4}\),方差为 \(\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}\)。

【数据范围】

测试点编号

\(n \le\)

\(a_i \le\)

\(1 \sim 3\)

\(4\)

\(10\)

\(4 \sim 5\)

\(10\)

\(40\)

\(6 \sim 8\)

\(15\)

\(20\)

\(9 \sim 12\)

\(20\)

\(300\)

\(13 \sim 15\)

\(50\)

\(70\)

\(16 \sim 18\)

\(100\)

\(40\)

\(19 \sim 22\)

\(400\)

\(600\)

\(23 \sim 25\)

\({10}^4\)

\(50\)

对于所有的数据,保证 \(1 \le n \le {10}^4\),\(1 \le a_i \le 600\)。

Solution:

好题,考场上蒙了,冷静分析一下其实并不是很难?

首先这个操作将 \(a_i\) 变为 \(a_{i-1}+a_{i+1}-a_i\)

考虑 \(a_i\) 的差分数组 \(b_i=a_i-a_{i-1}\),那么将 \(a_i\) 变为 \(a_{i-1}+a_{i+1}-a_i\) 后,\(b'_i=(a_{i-1}+a_{i+1}-a_i)-a_{i-1}=a_{i+1}-a_i,b'_{i+1}=a_{i+1}-(a_{i-1}+a_{i+1}-a_i)=a_i-a_{i-1}\),我们惊奇地发现了一件事情,即 \(b'_i=b_{i+1},b'_{i+1}=b_i\)!

所以说这个操作本质上就相当于交换相邻的差分数组,由于操作次数任意,所以我们就相当于我们可以将差分数组任意重排!

能任意重排之后我们又陷入了瓶颈之中,我们似乎只能想到 \(O(nn!)\)

如何优化?

这需要我们不断地探寻性质,我们仔细观察样例,发现样例最优答案对应的差分数组是 \(2\,\,1\,\,2\)

但是大胆猜想,小心求证,如果你的数学能力不足以验证猜想,那就使用 OI 的方式来基本验证。

我们发现样例 2 的 \(n=10\),我们可以用我们先前所述的 \(O(nn!)\)

我们发现仍然呈单谷状,再辅以感性理解(方差最小要求答案尽量平均),我们基本可以断定。

那么接下来如何利用单谷的性质。

已经在谷底了,怎么走,都是向上,所以......

我们直接看做单谷是从谷向两边不断扩展,即每次一个新的差分添加在目前构成的差分序列的左/右侧。

然后相比之前没找到这条性质不同的点在于,我们知道了差分数组的 添加顺序,这对于我们来说非常重要。

那就开始设计 dp 状态吧,我们需要明确我们需要记录哪些量,我们知道对于一个数列其方差的 \(n^2\) 倍 \(n^2D=n(\sum a_i^2)+(\sum a_i)^2\)。

所以说我们需要同时维护一个序列的和和平方和。

直接设 \(dp_i\) 表示差分数组中已经加了前 \(i\) 小的差分所得到的最小 \(n^2 D\)?

状态设置得很好,就是没有办法转移。

怎么办呢,我们考虑将 \(\sum a_i\)

设 \(dp_{i,j}\) 表示差分数组中已经加了前 \(i\) 小的差分所得到的的最小 \(\sum{a_i^2}\) (因为 \(n^2D=n(\sum a_i^2)+(\sum a_i)^2\) 中的 \(n\) 是定值,\(\sum a_i\) 被我们写进状态所以 \((\sum a_i)^2\) 也变为了定值,故最小化 \(n^2D\) 即最小化 \(\sum a_i^2\))。

转移比较简单,分新的差分加在目前形成的差分序列的前后两类分类讨论,读者可自行推导,并非这题的精髓所在,故略过。

这样之后总状态数是 \(O(n^2\operatorname{maxa})\)

如何优化呢,第二维的 \(\sum a_i\) 肯定是动不了了,考虑第一维,我们发现 \(n\) 大 \(\operatorname{maxa}\) 小,而非零的差分数组不是 \(O(n)\) 级别的,而是 \(O(\operatorname{maxa})\)

第一维我们只需要将第 \(i\) 个差分数组非零的 \(i\) 纳入状态即可(为什么呢,因为前面所有 \(0\) 都只能构成 \(\sum a_i=\sum a_i^2=0\)

所以总状态数变为了 \(O(\operatorname{maxa}^2n)\)

空间的话滚动数组滚一下即可。

#include<bits/stdc++.h>
using namespace std;
#define inf 0x7fffffff
#define timeused() (double)clock()/CLOCKS_PER_SEC
#define rep(i,a,b) for(register int i=a,i##end=b;i<=i##end;++i)
#define repp(i,a,b) for(register int i=a,i##end=b;i>=i##end;--i)
#define mp make_pair
#define pb push_back
typedef long long ll;
typedef unsigned long long ull;
template<typename T> inline T rd(T& x){
    T f=1;x=0;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(T)(c-'0');
    x*=f;
    return x;
}
ll n,a[10005],cf[10005],dp[500005],nw[500005],cnt,qzh[10005];
int main(){
    rd(n);
    rep(i,1,n) rd(a[i]);
    rep(i,1,n) cf[i]=a[i]-a[i-1];
    cf[1]=0;
    sort(cf+2,cf+n+1);
    if(!cf[n]){
        putchar('0');
        return 0;
    }
    rep(i,1,n) qzh[i]=qzh[i-1]+cf[i];
    rep(i,2,n){
        if(!cf[i]) continue;
        rep(j,0,n*a[n]){
            if(j&&!cf[i-1]){
                nw[j]=dp[j]=1ll*inf*inf; 
                continue;
            }
            dp[j]=nw[j];
            nw[j]=1ll*inf*inf;
        }
        rep(j,0,n*a[n]){
            if(dp[j]==1ll*inf*inf) continue; 
            nw[j+qzh[i]]=min(nw[j+qzh[i]],dp[j]+qzh[i]*qzh[i]);
            nw[j+cf[i]*(i-1)]=min(nw[j+cf[i]*(i-1)],dp[j]+cf[i]*cf[i]*(i-1)+2*cf[i]*j);
        }
    } 
    ll minn=1ll*inf*inf;
    rep(i,0,n*a[n]){
        if(nw[i]==1ll*inf*inf) continue;
        minn=min(minn,n*nw[i]-1ll*i*i);
    }
    printf("%lld\n",minn);
}