题目背景
九条可怜是一个热爱运动的女孩子。
题目描述
这一天她去爬山,她的父亲为了她的安全,雇了一些保镖,让他们固定地呆在在山的某些位置,来实时监视九条可怜,从而保护她。
具体来说,一座山可以描述为一条折线,折线的下方是岩石。这条折线有 nn 个折点,每个折点上有一个亭子,第 ii 个折点的坐标是 (i,h_i)(i,hi) 。九条可怜只可能会在亭子处玩耍,那些保镖也只会在亭子处监视可怜。
由于技术方面的原因,一个保镖只能监视所有他能看得到的,横坐标不超过他所在位置的亭子。我们称一个保镖能看到一个亭子 pp ,当且仅当他所在的亭子 qq 和 pp 的连线不经过任何一块岩石。特别地,如果这条连线恰好经过了除了 p,qp,q 以外的亭子,那么我们认为保镖看不到可怜。
雇佣保镖是一件很费钱的事情,可怜的父亲希望保镖越少越好。
可怜的父亲还希望得到详尽的雇佣保镖的方案,他知道有些亭子可能正在维修,他想对所有的 1\leq l\leq r\leq n1≤l≤r≤n计算:如果事先已知了只有区间 [l,r][l,r] 的亭子可以用来玩耍(和监视),那么最少需要多少个保镖,才能让 [l,r][l,r] 中的每一个亭子都被监视到。
可怜的父亲已经得到了一个结果,他希望和你核实他的结果是否正确。
输入输出格式
输入格式:第一行输入一个整数 nn 表示亭子的数目。 接下来一行 nn 个整数,第 ii 个整数 h_ihi 表示第 ii 个亭子的坐标是 (i,h_i)(i,hi) 。
输出格式:对所有的 1\leq l\leq r\leq n1≤l≤r≤n 计算:如果事先已知了可怜只会在 [l,r][l,r] 这个区间的亭子里面玩耍,那么最少需要多少个保镖,才能让 $[l,r] 中的每一个亭子都被监视到。由于输出量太大,可怜的父亲只要你输出所有$ [l,r]$的答案的异或即可。
输入输出样例
说明
样例解释
如果 r-l+1\leq 2r−l+1≤2 ,那么答案显然是 11 。 如果 l=1,r=nl=1,r=n ,那么答案是 22 ,需要安排两个保镖在 (2,3),(3,1)(2,3),(3,1)两个位置监视可怜。
题解:
所以我们定 f[l][r]代表l到r间的最优解,我们可以找到所有能被上面监视的点然后枚举是否安插一个保镖,以此进行转移!
从前到后枚举r,在从后到前枚举l即可。
说实话这个转移我看的也有点迷。。
代码(我的):
#include<bits/stdc++.h>
using namespace std;
int n,i,ans,j,now,sum,a[10001],f[5001][5001];
double pd(int l,int now,int r){
return ((double)a[r]-a[now])/((double)r-now)-((double)a[now]-a[l])/((double)now-l);
}
int main(){
scanf("%d",&n);
for(i=1;i<=n;i++)scanf("%d",&a[i]);
for(i=1;i<=n;i++){
now=i;sum=1;
f[i][i]=1;ans^=1;
for(j=i-1;j;j--){
if(now==i||pd(j,now,i)>0.0){
sum+=min(f[j+1][now-1],f[j+1][now]);
now=j;
}
f[j][i]=sum+min(f[j][now-1],f[j][now]);
ans^=f[j][i];
}
}
printf("%d",ans);
}
代码(大牛的,超多注释,配合注释看应该可以大致看懂的):
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<string>
#include<algorithm>
#include<queue>
using namespace std;
const int inf=1e9;
const int MAXN=5005;
int f[MAXN][MAXN],h[MAXN],n,m,k,ans=0;
// f[l][r]表示如果只监视l到r需要多少个保镖
char readchar(){
static char buf[100000], *l=buf,*r=buf;
if(l==r) r=(l=buf)+fread(buf,1,100000,stdin);
if(l==r) return EOF;
return *l++;
}
inline int read()
{
int x=0,f=1;char ch=readchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=readchar();}
while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=readchar();}
return x*f;
}
long long cross(int u,int v,int w)//检验v所在的点会不会挡住w监视u的视线
{
return 1LL*(v-u)*(h[w]-h[u])-1LL*(w-u)*(h[v]-h[u]);
// 这可以画一个方格图证明一下,用斜率推也可以推出来;
// 如果这个v点挡不住视线,那会返回一个正数,反之则返回一个负数;
}
int getdp(int l,int r)
{
if(l>r) return 0;
return f[l][r];
}
//标程是这样写的,可能是为了避免l>r造成的问题;
int main()
{
freopen("sw.in","r",stdin);
freopen("sw.out","w",stdout);
n=read();
for(int i=1;i<=n;i++) h[i]=read();
for(int r=1;r<=n;r++){
int pre=r,cur=1;
f[r][r]=1;
for(int l=r-1;l>=1;l--){
if(pre==r||cross(l,pre,r)>0){//如果pre点挡不住r的视线
cur+=min(getdp(l+1,pre-1),getdp(l+1,pre));
//可以在l+1到pre这个点或者到pre之前这个点间寻找最优解并加入答案
//因为pre这个点可以给一个保镖,也可以不给一个!
pre=l;//把枚举的这个障碍物赋给pre
//因为在l这个点能被r监视到,所以下一个枚举的点如果无法被它挡住,那一定没有点挡得住;
}
f[l][r]=cur+min(getdp(l,pre-1),getdp(l,pre));
//l到r的最优解
//顺便将l到pre这个点的没有加过的最优解加上
}
for(int l=1;l<r;l++) ans^=f[l][r];//处理答案的异或问题
}
if(n&1) ans^=1;
//因为每个f[i][i]也要参与异或操作
//如果n是奇数,那会进行异或1操作奇数次,那就相当于异或一次;
//如果n是偶数,那就不用异或了,因为异或1两次跟没异或效果是一样的!
cout<<ans<<endl;
return 0;
}
区间dp
ti
91≤hi<=10^9