BZOJ5324 JXOI2018 守卫

传送门

这是我见过的为数不多的良心九怜题之一

题目大意

给定一段$n$个点构成的折线,第$i$个折点的坐标是$(i,h_i)$,你可以在$i$点放置一个视野,定义$i$能看到$j$当且仅当$i$处有视野且$j\leq i$且$(i,h_i)$到$(j,h_j)$的连线段除了两个端点都严格地在折线上方。一段区间$[L,R]$对答案的贡献是能看到至少整个$[L,R]$的需要的视野最小数量,求所有区间答案的异或和。

题解

考虑一段区间$[L,R]$,$R$一定要选,对于每一个$R$端点看不到的点$x$,若$x+1$能被看到,则一定要在$x+1$处或$x$处放置视野才行。

所以区间$DP$,预处理两点之间可否互相看到,枚举右端点,从右向左扫,对于最左侧连续的一段$r$看不到的点,设$[l,k]$是这段区间,则$F_{l,r}=\min\{F_{l,k},F_{l,k+1}\}+F_{k+1,r}$,否则$F_{l,r}=F_{l+1,r}$。

#include<bits/stdc++.h>
#define M 5020
#define LL long long
using namespace std;
int n,m,p[M],F[M][M],ans; bool can[M][M];
int read(){
    int nm=0,fh=1; char cw=getchar();
    for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
    for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
    return nm*fh;
}
int main(){
    n=read(); for(int i=1;i<=n;i++) p[i]=read(),F[i][i]=F[i-1][i]=1;
    for(int i=1;i<=n;i++){
        for(int ht=-1,j=1,pos=1;i+j<=n;j++){
            if((LL)(ht-p[i])*(LL)j>=(LL)(p[i+j]-p[i])*(LL)pos) can[i][i+j]=false;
            else can[i][i+j]=true,pos=j,ht=p[i+j];
        }
    } ans=1;
    for(int r=3;r<=n;++r){
        for(int last=0,l=r-2;l;--l){
            if(can[l][r]) last=0,F[l][r]=F[l+1][r];
            else if(last) F[l][r]=F[last+1][r]+min(F[l][last],F[l][last+1]);
            else F[l][r]=F[l+1][r]+1,last=l; ans^=F[l][r];
        }
    } printf("%d\n",ans); return 0;
}

猜你喜欢

转载自www.cnblogs.com/OYJason/p/9821796.html