这是我见过的为数不多的良心九怜题之一
题目大意
给定一段$n$个点构成的折线,第$i$个折点的坐标是$(i,h_i)$,你可以在$i$点放置一个视野,定义$i$能看到$j$当且仅当$i$处有视野且$jleq 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 ",ans); return 0; }