题面
https://www.luogu.org/problem/P4563
题解
一步杀神仙题。
首先,对于一段区间$[l..r]$,$r$必须选。
$r$选以后,能被看到的元素必然是和$r$斜率的后缀最小值。
根据$mbox{Gloid}$爷的神仙性质,对于一段不能被看到的区间$[x..y]$(后缀最小值在$y+1$处),不可以被$(y+1..r]$的守卫看到。
我们来感性理解一下这个结论,即对于$[x..y]$中任意一点$a$,我们把$a$和$y+1$连线, 没有点在这条连线上方。
若某个点$b(y+1<b<r)$在连线上方,则$r$就看不到$y+1$了。
所以我们知道,要解决$[x..y]$,只能在$y$或$y+1$处放守卫。
$f[l][r]$代表区间$[l..r]$的答案(在$r$处必放守卫)
设$p$为当前最近的一个后缀最小值,显然有转移:
$$f[l][r]=min(f[l][p],f[l][p-1])+f[p][r]$$
固定$r$,反求$l$即可。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define ri register int #define N 5050 using namespace std; int sum; int n,h[N]; int f[N][N]; double slope(int l,int r) { return (double)(h[r]-h[l])/(r-l); } bool cansee(int l,int x,int r) { return slope(x,r)>slope(l,r); } int main() { int ans=0; scanf("%d",&n); for (ri i=1;i<=n;i++) scanf("%d",&h[i]); for (ri r=1;r<=n;r++) { f[r][r]=1; ans^=f[r][r]; int sum=1,p=0; for (ri l=r-1;l>=1;l--) { if (!p || cansee(l,p,r)) sum=f[l+1][r],p=l; f[l][r]=sum+min(f[l][p-1],f[l][p]); ans^=f[l][r]; } } cout<<ans<<endl; }