PS:如果读过题了可以跳过题目描述直接到题解部分
提交链接:洛谷 P4983 忘情
题目
题目背景
“为什么要离开我!”
“因为你没玩儿转!”
“我玩儿转了!”
“那好,你现在就给我维护这么一个式子!”
“为什么要出这么毒瘤的东西。”
“为了恶心你。”
“…”
… … … … … … … … … … … . ……………………………. …………………………….
题目描述
你的 n p y npy npy 为了恶心你,特地请了四位大神和一个辣鸡!
h d x r i e \rm hdxrie hdxrie 说:“我们得求和。”于是有了 Σ i = 1 n x i \Sigma_{i=1}^{n}x_i Σi=1nxi 。
I m a g i n e \rm Imagine Imagine 说:“我们得有平均数。”于是有了 x ˉ \bar x xˉ 。
T i m e T r a v e l l e r \rm TimeTraveller TimeTraveller 说:“我们得有加减乘除。”于是有了一些恶心的组合。
A l t h e n ⋅ W a y ⋅ S a t a n \rm Althen·Way·Satan Althen⋅Way⋅Satan 说:“我们还得有平方。”于是我们将它平方。
最垃圾的 Z r e d X N y \rm ZredXNy ZredXNy 说:“那我帮你们整合一下。”
于是,我们得到了这么一个式子 : : :
( ( Σ i = 1 n x i × x ˉ ) + x ˉ ) 2 x ˉ 2 \frac{\left((\Sigma_{i=1}^{n}x_i×\bar x)+\bar x\right)^2}{\bar x^2} xˉ2((Σi=1nxi×xˉ)+xˉ)2
我们定义一段序列的值为这个,其中 n n n为此序列的元素个数。
我们给定一段长度为 n n n 的序列,现在要求将它分成 m m m 段,要求每一段的值的总和最小,求出这个最小值。
输入格式
第一行两个正整数,分别为 n n n, m m m,定义见题面。
接下来一行为 n n n 个正整数,依次给出这个序列的每个元素的值 x i x_i xi 。
输出格式
一个整数,求出这个最小值。
样例 #1
样例输入 #1
3 2
1 2 3
样例输出 #1
32
样例 #2
样例输入 #2
10 3
1 2 3 4 5 6 7 8 9 10
样例输出 #2
1140
提示
-
对于 30 % 30 \% 30% 的数据, m ≤ n ≤ 500 m≤n≤500 m≤n≤500;
-
另有 20 % 20 \% 20% 的数据,保证 m = 2 m=2 m=2;
-
对于 100 % 100 \% 100% 的数据, m ≤ n ≤ 100000 m≤n≤100000 m≤n≤100000, 1 ≤ x i ≤ 1000 1≤x_i≤1000 1≤xi≤1000。
题解
我们先来化简一下这道题要求的东西:
(
(
Σ
i
=
1
n
x
i
×
x
ˉ
)
+
x
ˉ
)
2
x
ˉ
2
\frac{\left((\Sigma_{i=1}^{n}x_i×\bar x)+\bar x\right)^2}{\bar x^2}
xˉ2((Σi=1nxi×xˉ)+xˉ)2
=
(
(
Σ
i
=
1
n
x
i
)
2
n
+
Σ
i
=
1
n
x
i
n
)
2
(
Σ
i
=
1
n
x
i
n
)
2
=\frac{(\frac{(\Sigma_{i=1}^{n}x_i)^2}{n}+\frac{\Sigma_{i=1}^{n}x_i}{n})^2}{(\frac{\Sigma_{i=1}^{n}x_i}{n})^2}
=(nΣi=1nxi)2(n(Σi=1nxi)2+nΣi=1nxi)2
=
(
Σ
i
=
1
n
x
i
n
)
2
×
(
1
+
Σ
i
=
1
n
x
i
n
)
2
(
Σ
i
=
1
n
x
i
n
)
2
=\frac{(\frac{\Sigma_{i=1}^{n}x_i}{n})^2\times(1+\frac{\Sigma_{i=1}^{n}x_i}{n})^2}{(\frac{\Sigma_{i=1}^{n}x_i}{n})^2}
=(nΣi=1nxi)2(nΣi=1nxi)2×(1+nΣi=1nxi)2
=
(
1
+
Σ
i
=
1
n
x
i
n
)
2
=(1+\frac{\Sigma_{i=1}^{n}x_i}{n})^2
=(1+nΣi=1nxi)2
为了方便接下来的表示,我们令
S
i
S_i
Si表示序列第
i
i
i段中所有元素的和。
原式
=
(
1
+
S
1
)
2
+
(
1
+
S
2
)
2
+
.
.
.
+
(
1
+
S
m
)
2
原式=(1+S_1)^2+(1+S_2)^2+...+(1+S_m)^2
原式=(1+S1)2+(1+S2)2+...+(1+Sm)2
=
1
+
2
×
S
1
+
S
1
2
+
1
+
2
×
S
2
+
S
2
2
+
.
.
.
+
1
+
2
×
S
m
+
S
m
2
=1+2\times S_1+S_1^2+1+2\times S_2+S_2^2+...+1+2\times S_m+S_m^2
=1+2×S1+S12+1+2×S2+S22+...+1+2×Sm+Sm2
=
m
+
2
×
Σ
i
=
1
n
x
i
+
Σ
i
=
1
m
S
i
=m+2\times \Sigma_{i=1}^nx_i+\Sigma_{i=1}^mS_i
=m+2×Σi=1nxi+Σi=1mSi
显然当序列一定的时候可以分成的段数越多,值的总和越小,所以题目中的m给了多大,我们就要每一段都分掉,而不会有空段。
20pts
我考试的时候,还不太写得来斜率优化,于是就写的dfs,严重超时。
100pts
它每分一段的时候原本是没有任何代价的,所以分的段数越多,就一定越优。而题目中有对段数的限定,这是十分不好处理的,所以我们换一个限定方式。
假设在它每分一段的时候都有了一个代价,那么我们会发现,代价增加的同时,最优的段数会逐渐减少,所以我们只需要二分这个代价的大小,直到它的最优段数等于我们在题目中限定的段数的时候,用我们求到的答案减去代价与段数的乘积(也就是总代价)就是我们最后要求的答案了。
代码实现
20pts
//洛谷 P4983 忘情
#pragma GCC optimize(3)
#include<cstdio>
#include<iostream>
using namespace std;
long long n,m;
long long x[100010];
long long s[100010];
long long ans;
int a[100010];
void re(long long &x){
int nt;
x=0;
while(!isdigit(nt=getchar()));
x=nt^'0';
while(isdigit(nt=getchar())){
x=(x<<3)+(x<<1)+(nt^'0');
}
}
long long dfs(int i,int j){
a[j]=i;
long long ans=0;
if(j==m-1){
for(i=1;i<=m;++i){
ans+=1ll*(s[a[i]]-s[a[i-1]])*(s[a[i]]-s[a[i-1]]);
}
return ans;
}
int k=i+1;
while(s[k]-s[i]<(s[n]/m)&&n-k>=j){
++k;
}
ans=min(dfs(k+1,j+1),min(dfs(k-1,j+1),dfs(k,j+1)));
return ans;
}
int main(){
register int i;
re(n),re(m);
for(i=1;i<=n;++i){
re(x[i]);
s[i]=s[i-1]+x[i];
}
a[m]=n;
ans=dfs(0,0)+m+2ll*s[n];
printf("%lld\n",ans);
return 0;
}
100pts
//洛谷 P4983 忘情
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const long long inf=1e18;
long long n,m;
long long x[100010];
long long s[100010];
long long ans;
long long f[100010];
long long g[100010];
long long q[100010];
void re(long long &x){
int nt;
x=0;
while(!isdigit(nt=getchar()));
x=nt^'0';
while(isdigit(nt=getchar())){
x=(x<<3)+(x<<1)+(nt^'0');
}
}
void check(long long mid){
memset(f,0x3f,sizeof(f));
memset(g,0,sizeof(g));
f[0]=0;
int l=1,r=1;
q[1]=0;
for(int i=1;i<=n;++i){
while(l<r&&((long double)(f[q[l+1]]+s[q[l+1]]*s[q[l+1]]-2*s[q[l+1]]-f[q[l]]-s[q[l]]*s[q[l]]+2*s[q[l]]*1.0)/(s[q[l+1]]-s[q[l]])<2*s[i])){
++l;
}
f[i]=f[q[l]]+(s[i]-s[q[l]]+1)*(s[i]-s[q[l]]+1)+mid;
g[i]=g[q[l]]+1;
while(l<r&&(long double)(f[q[r]]+s[q[r]]*s[q[r]]-2*s[q[r]]-f[q[r-1]]-s[q[r-1]]*s[q[r-1]]+2*s[q[r-1]])*1.0/(s[q[r]]-s[q[r-1]])>(long double)(f[i]+s[i]*s[i]-2*s[i]-f[q[r-1]]-s[q[r-1]]*s[q[r-1]]+2*s[q[r-1]])/(s[i]-s[q[r-1]])){
--r;
}
q[++r]=i;
}
}
int main(){
register int i;
re(n),re(m);
for(i=1;i<=n;++i){
re(x[i]);
s[i]=s[i-1]+x[i];
}
long long l,r,mid;
l=0,r=inf,ans=0;
while(l<=r){
long long mid=l+r>>1;
check(mid);
if(g[n]<=m){
ans=mid;
r=mid-1;
}
else{
l=mid+1;
}
}
check(ans);
printf("%lld\n",f[n]-m*ans);
return 0;
}