Flute很喜欢柠檬。它准备了一串用树枝串起来的贝壳,打算用一种魔法把贝壳变成柠檬。贝壳一共有 N (1 ≤ N ≤ 100,000)只,按顺序串在树枝上。为了方便,我们从左到右给贝壳编号 1…N。每只贝壳的大小不一定相同,贝壳 i 的大小为 si(1 ≤ si ≤10,000)。变柠檬的魔法要求,Flute 每次从树枝一端取下一小段连续的贝壳,并选择一种贝壳的大小 s0。如果 这一小段贝壳中 大小为 s0 的贝壳有 t 只,那么魔法可以把这一小段贝壳变成 s0t^2 只柠檬。Flute可以取任意多次贝壳,直到树枝上的贝壳被全部取完。各个小段中,Flute 选择的贝壳大小 s0 可以不同。而最终 Flute得到的柠檬数,就是所有小段柠檬数的总和。Flute 想知道,它最多能用这一串贝壳变出多少柠檬。请你帮忙解决这个问题。
这道题首先发现最优解分成的每个段首尾肯定是s0,不然就不需要它们当首尾。
那我们就可以列出dp方程,
f
[
i
]
=
m
i
n
s
[
j
+
1
]
=
=
s
[
i
]
(
f
[
j
]
+
(
s
u
m
[
i
]
−
s
u
m
[
j
+
1
]
+
1
)
2
∗
s
[
i
]
(
或
s
[
j
+
1
]
)
)
f[i]=min_{s[j+1]==s[i]}(f[j]+(sum[i]-sum[j+1]+1)^2*s[i](或s[j+1]))
f[i]=mins[j+1]==s[i](f[j]+(sum[i]−sum[j+1]+1)2∗s[i](或s[j+1])),其中sum[i]表示
∑
j
=
1
i
[
s
[
j
]
=
=
s
[
i
]
]
\sum_{j=1}^i [s[j]==s[i]]
∑j=1i[s[j]==s[i]]。但我们发现这个dp时间复杂度是n^2,那么就要考虑优化。
作为一位熟练的oi选手,我们先把方程化简,为
f
[
j
]
+
s
[
j
+
1
]
∗
(
s
u
m
[
j
+
1
]
2
−
2
∗
s
u
m
[
j
+
1
]
)
=
2
∗
s
u
m
[
i
]
∗
s
[
i
]
∗
s
u
m
[
j
+
1
]
+
f
[
i
]
−
s
[
i
]
∗
(
s
u
m
[
i
]
2
+
2
∗
s
u
m
[
i
]
+
1
)
f[j]+s[j+1]*(sum[j+1]^2-2*sum[j+1])=2*sum[i]*s[i]*sum[j+1]+f[i]-s[i]*(sum[i]^2+2*sum[i]+1)
f[j]+s[j+1]∗(sum[j+1]2−2∗sum[j+1])=2∗sum[i]∗s[i]∗sum[j+1]+f[i]−s[i]∗(sum[i]2+2∗sum[i]+1)。考虑线性规划,把j化为一个点,横坐标为
2
∗
s
u
m
[
j
+
1
]
2*sum[j+1]
2∗sum[j+1],纵坐标为
f
[
j
]
+
s
[
j
+
1
]
∗
(
s
u
m
[
j
+
1
]
2
−
2
∗
s
u
m
[
j
+
1
]
)
f[j]+s[j+1]*(sum[j+1]^2-2*sum[j+1])
f[j]+s[j+1]∗(sum[j+1]2−2∗sum[j+1]),那么最优决策点就是使一条斜率为
s
u
m
[
i
]
∗
s
[
i
]
sum[i]*s[i]
sum[i]∗s[i]的直线过此点时的截距比其他点的都要大。
那么很显然是要维护一个上凸壳(下凸壳一定不是最优解,可以自己画一下)。在找最大截距时由于sum[i]*s[i]不是递增的,所以需要二分,因为又是很显然发现最优决策点只要满足前一条直线的斜率
>
s
u
m
[
i
]
∗
s
[
i
]
>sum[i]*s[i]
>sum[i]∗s[i],后一条
<
s
u
m
[
i
]
∗
s
[
i
]
<sum[i]*s[i]
<sum[i]∗s[i]就行了,如果没有就取最后的点。然后维护好这个凸壳就好了。
那么这道题就做完了大雾。
update:我sb了,s[i]*sum[i]是递增的,因为s[i]是定值。那么就可以不用写二分,直接维护,时间复杂度是O(n)的。这个做法很显然我就不写了吧。好吧其实是因为我懒
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdlib>
#include<vector>
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0' && ch<='9')x=x*10+ch-'0',ch=getchar();
return x*f;
}
inline void write(int x)
{
if(x<0)putchar('-'),x=-x;
if(x>9)write(x/10);
putchar(x%10+'0');
}
inline void pr1(int x){write(x),putchar(' ');}
inline void pr2(int x){write(x),puts("");}
vector<int>p[10010];
int sum[100010],tot[10010],w[100010];
long long f[100010];
inline int X(int id){return 2*sum[id+1];}
inline long long Y(int id){return f[id]+1LL*w[id+1]*sum[id+1]*(sum[id+1]-2);}
inline int pf(int x){return x*x;}
inline int erfen(int id,int limit)
{
int l=1,r=p[id].size()-1,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(Y(p[id][mid])-Y(p[id][mid-1])>=1LL*limit*(X(p[id][mid])-X(p[id][mid-1])))ans=mid,l=mid+1;
else r=mid-1;
}return p[id][ans];
}
int main()
{
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
int n=read();
for(int i=1;i<=n;i++)w[i]=read(),sum[i]=++tot[w[i]];
p[w[1]].push_back(0);
for(int i=1;i<=n;i++)
{
int ans=erfen(w[i],sum[i]*w[i]);
f[i]=f[ans]+1LL*pf(sum[i]-sum[ans+1]+1)*w[i];
while(p[w[i+1]].size()>1 && (Y(i)-Y(p[w[i+1]][p[w[i+1]].size()-1]))*(X(p[w[i+1]][p[w[i+1]].size()-1])-X(p[w[i+1]][p[w[i+1]].size()-2]))>=(Y(p[w[i+1]][p[w[i+1]].size()-1])-Y(p[w[i+1]][p[w[i+1]].size()-2]))*(X(i)-X(p[w[i+1]][p[w[i+1]].size()-1])))p[w[i+1]].pop_back();
p[w[i+1]].push_back(i);
}printf("%lld\n",f[n]);
return 0;
}