测试地址:守卫
做法:本题需要用到区间DP+优化。
看到数据范围,容易想到令
f(l,r)
f
(
l
,
r
)
为区间
[l,r]
[
l
,
r
]
的答案,我们来考虑怎么转移。
对于一个区间
[l,r]
[
l
,
r
]
,首先点
r
r
是一定要有人的,对于点,它能看到的所有点可以这样求:从点
r−1
r
−
1
开始,如果它到点
r
r
的斜率和上一个能看到的点到点的斜率相比更小,那么当前点就能看到,否则就看不到(可以把坐标系转换为以点
r
r
为极点的极坐标系来考虑),这样从右往左扫一遍就可以求出它能看到的所有点了。于是我们可以预处理出这些信息。
那我们有了这些信息,再考虑每一个点
r
r
看不到的点的连续区间,注意到对于
x>rk+1
x
>
r
k
+
1
,点
x
x
都是不可能看到区间中的点的,这个结论画画图也可以得出来。由这个结论我们可以得出,每个这样的区间的决策对整个区间
[l,r]
[
l
,
r
]
来说,贡献是独立的,而对于一个区间
[lk,rk]
[
l
k
,
r
k
]
,要使得里面的点全部被看到,可以选择在点
rk
r
k
或点
rk+1
r
k
+
1
布置一个人,贡献分别为
f(lk,rk)
f
(
l
k
,
r
k
)
和
f(lk,rk+1)
f
(
l
k
,
r
k
+
1
)
,因此我们有状态转移方程:
f(l,r)=1+∑min(f(lk,rk),f(lk,rk+1))
f
(
l
,
r
)
=
1
+
∑
min
(
f
(
l
k
,
r
k
)
,
f
(
l
k
,
r
k
+
1
)
)
这个方程是
O(n3)
O
(
n
3
)
的,显然不能通过此题,这就需要我们的优化。从方程本身的角度已经很难优化下去了,因此我们对求方程的方法进行优化。
注意到
rk+1<r
r
k
+
1
<
r
,因此我们从小到大枚举
r
r
来确保当前状态所需要的状态都已经被计算,而在固定的情况下,
l
l
左移时,经过的看不到的连续区间的贡献就可以顺便记录下来,这样我们就可以做到
O(n2)
O
(
n
2
)
的总时间复杂度了,于是我们就完成了这一题。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
ll n,h[5010],f[5010][5010];
bool see[5010][5010];
int main()
{
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
scanf("%lld",&h[i]);
for(ll i=1;i<=n;i++)
{
see[i][i]=0;
for(ll j=i-1,last=0;j>=1;j--)
{
if (!last||(h[i]-h[j])*(i-last)<(h[i]-h[last])*(i-j))
{
see[i][j]=1;
last=j;
}
else see[i][j]=0;
}
}
ll ans=0;
for(ll r=1;r<=n;r++)
{
ll lastans=1,lastr=0;
for(ll l=r;l>=1;l--)
{
if (see[r][l])
{
if (lastr)
{
lastans+=min(f[l+1][lastr],f[l+1][lastr+1]);
lastr=0;
}
}
else
{
if (!lastr) lastr=l;
f[l][r]+=min(f[l][lastr],f[l][lastr+1]);
}
f[l][r]+=lastans;
ans^=f[l][r];
}
}
printf("%lld",ans);
return 0;
}