转移方程:
d
p
[
i
]
=
m
i
n
(
d
p
[
i
]
,
d
p
[
j
]
+
(
x
−
L
)
2
)
,
0
<
=
j
<
=
i
−
1
dp[i]=min(dp[i],dp[j]+(x-L)^2) , 0<=j<=i-1
dp[i]=min(dp[i],dp[j]+(x−L)2),0<=j<=i−1
但是如果只是单纯的n2 爆破大概率超时,(但就是有爆破神仙过了),这里的优化想法是这样的,如果说能直接找到i对应的j中最优秀的那个j就能很好地优化这个过程。
先处理一下前缀和,sum[i]表示第i位及以前的体积和,那么原式即为:
d
p
[
i
]
=
m
i
n
(
d
p
[
i
]
,
d
p
[
j
]
+
(
s
u
m
[
i
]
+
i
−
s
u
m
[
j
]
−
j
−
L
−
1
)
2
)
(
j
<
i
)
dp[i]=min(dp[i],dp[j]+(sum[i]+i−sum[j]−j−L−1) ^2)(j<i)
dp[i]=min(dp[i],dp[j]+(sum[i]+i−sum[j]−j−L−1)2)(j<i)
我们想要作为j的两个选择中q点比p点更优,那么就有:
d
p
[
q
]
+
(
s
u
m
[
i
]
+
i
−
s
u
m
[
q
]
−
q
−
L
−
1
)
2
<
d
p
[
p
]
+
(
s
u
m
[
i
]
+
i
−
s
u
m
[
p
]
−
p
−
L
−
1
)
2
dp[q]+(sum[i]+i−sum[q]−q−L−1) ^2<dp[p]+(sum[i]+i−sum[p]−p−L−1) ^2
dp[q]+(sum[i]+i−sum[q]−q−L−1)2<dp[p]+(sum[i]+i−sum[p]−p−L−1)2
化简如下,令:
k
(
i
)
=
s
u
m
[
i
]
+
i
;
k(i)=sum[i]+i;
k(i)=sum[i]+i;
h
(
i
)
=
s
u
m
[
i
]
+
i
−
L
−
1
h(i)=sum[i]+i-L-1
h(i)=sum[i]+i−L−1
所以原式即为:
d
p
[
q
]
+
(
k
(
i
)
−
h
(
q
)
)
2
<
d
p
[
p
]
+
(
k
(
i
)
−
h
(
p
)
)
2
dp[q]+(k(i)-h(q))^2<dp[p]+(k(i)-h(p))^2
dp[q]+(k(i)−h(q))2<dp[p]+(k(i)−h(p))2
即
:
2
⋅
k
(
i
)
>
d
p
[
q
]
+
h
(
q
)
2
−
d
p
[
p
]
−
h
(
q
)
2
h
(
q
)
−
h
(
p
)
即: 2\cdot k(i)>\frac{{dp[q]+h(q)^{2} - dp[p]-h(q)^{2}}}{{ h(q)-h( p)}}
即:2⋅k(i)>h(q)−h(p)dp[q]+h(q)2−dp[p]−h(q)2
令
g
(
i
)
=
d
p
[
i
]
+
h
(
i
)
2
g(i) = dp[i]+h(i)^2
g(i)=dp[i]+h(i)2:
最
终
化
简
为
:
2
⋅
k
(
i
)
>
g
(
q
)
−
g
(
p
)
h
(
q
)
−
h
(
p
)
最终化简为: 2\cdot k(i)>\frac{{g(q)-g(p)}}{{ h(q)-h( p)}}
最终化简为:2⋅k(i)>h(q)−h(p)g(q)−g(p)
( g ( q ) − g ( p ) / ( h ( q ) − h ( p ) ) < 2 ⋅ K ( i ) (g( q )-g( p )/(h( q )-h( p ))<2\cdot K( i ) (g(q)−g(p)/(h(q)−h(p))<2⋅K(i)
• g(x),h(x),k(x)都是函数。
• 这是不是很像斜率?
• 我们判断两点哪个更优秀是不是可以通过
(h( p ),g( p ))和(h( q ) ,g( q ))两点间斜率大小来判断?
题解的意思已经很明了了,有了斜率的辅助,现在目标就只剩找到最优点了。
那么哪个点是当前最优点呢?显而易见,必须左斜率小于等于k(i)并且右斜率大于等于k(i)。我们看的出来这是一个凸包,满足单调性,且左边的点删了就删了不会影响,所以我们可以维护一个单调队列可以大幅度减少搜索时间。
至于删点的条件,如果补入的新点与之前的凸包构成图形不是凸包了,那么内凹的点就可以删了,这需要三个点来判断是否内凹,que[ tail ], que[ tail - 1 ], 和 i 点。
所以就能写出:
for (int i = 1; i <= n; i++)
{
while (head < tail && judge1(head, i)) head++;
dp[i] = dp[que[head]] + s(i, que[head]); //单调队列中同步更新dp[i]
while (head < tail && judge2(que[tail - 1], que[tail], i)) tail--;
que[++tail] = i;
}
然后写一下判断添加函数和判断删除函数,这里题目要求是不要用double,用long long,叉乘判断斜率大小即可,好像说是ll相乘会炸??但是我没遇到,大爷说是我跟着stl一起爆的……emmm不是很懂,但我还是喜欢叉乘不喜欢浮点运算
bool judge1(ll head, ll i){ //判断添加
ll up = g(que[head]) - g(que[head + 1]);
ll down = h(que[head]) - h(que[head + 1]);
if (up > down * 2 * k(i))
return true;
return false;
}
bool judge2(ll p1, ll p2, ll p3){ //判断删除
return (h(p2) - h(p3)) * (g(p1) - g(p3)) - (h(p1) - h(p3))
* (g(p2) - g(p3)) >= 0;
}
最后贴AC代码:
#include<bits/stdc++.h>
#define FOR(a, b, c) for(int a=b; a<=c; a++)
#define maxn 50005
#define maxm 55
#define hrdg 1000000007
#define inf 2147483647
#define llinf 9223372036854775807
#define ll long long
#define pi acos(-1.0)
#define ls p<<1
#define rs p<<1|1
using namespace std;
ll n, L, v[maxn], sum[maxn], dp[maxn];
ll head, tail, que[maxn];
inline ll read(){
char c=getchar();long long x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
ll k(ll x) {return sum[x] + x;}
ll h(ll x) {return k(x) + L + 1;}
ll g(ll x){
ll temp = h(x);
return dp[x] + temp * temp;
}
ll s(ll x, ll y) {
ll temp = k(x) - h(y);
return temp * temp;
}
bool judge1(ll head, ll i){
ll up = g(que[head]) - g(que[head + 1]);
ll down = h(que[head]) - h(que[head + 1]);
if (up > down * 2 * k(i))
return true;
return false;
}
bool judge2(ll p1, ll p2, ll p3){
return (h(p2) - h(p3)) * (g(p1) - g(p3)) - (h(p1) - h(p3))
* (g(p2) - g(p3)) >= 0;
}
int main()
{
n = read(); L = read();
for (ll i = 1; i <= n; i++)
{
v[i] = read();
sum[i] = sum[i - 1] + v[i];
}
//FOR(i, 1, n) cout<<sum[i]<<endl;
head = tail = 1;
for (ll i = 1; i <= n; i++)
{
while (head < tail && judge1(head, i)) head++;
dp[i] = dp[que[head]] + s(i, que[head]);
while (head < tail && judge2(que[tail - 1], que[tail], i)) tail--;
que[++tail] = i;
}
printf("%lld\n", dp[n]);
return 0;
}
/*
5 4
3 4 2 1 4
*/