【JXOI2018】守卫

题面 

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;
}

猜你喜欢

转载自www.cnblogs.com/shxnb666/p/11794264.html