中国石油大学第六场个人训练赛 守卫(区间DP)

6888: 守卫

时间限制: 1 Sec  内存限制: 512 MB
提交: 79  解决: 25
[提交] [状态] [讨论版] [命题人:admin]

题目描述

九条可怜是一个热爱运动的女孩子。 这一天她去爬山,她的父亲为了她的安全,雇了一些保镖,让他们固定地呆在在山的某些位置,来实时监视九条可怜,从而保护她。 具体来说,一座山可以描述为一条折线,折线的下方是岩石。这条折线有n个折点,每个折点上有一个亭子,第i个折点的坐标是(i,hi)。九条可怜只可能会在亭子处玩耍,那些保镖也只会在亭子处监视可怜。 由于技术方面的原因,一个保镖只能监视所有他能看得到的,横坐标不超过他所在位置的亭子。我们称一个保镖能看到一个亭子p,当且仅当他所在的亭子q和p的连线不经过任何一块岩石。特别地,如果这条连线恰好经过了除了p,q以外的亭子,那么我们认为保镖看不到可 怜。 雇佣保镖是一件很费钱的事情,可怜的父亲希望保镖越少越好。 可怜的父亲还希望得到详尽的雇佣保镖的方案,他知道有些亭子可能正在维修,他想对所有的1≤L≤R≤n计算:如果事先已知了只有区间[L,R]的亭子可以用来玩耍(和监视),那么最少需要多少个保镖,才能让[L,R]中的每一个亭子都被监视到。 可怜的父亲已经得到了一个结果,他希望和你核实他的结果是否正确。

输入

第一行输入一个整数n表示亭子的数目。
接下来一行n个整数,第i个整数hi表示第i个亭子的坐标是(i,hi)。

输出

对所有的L≤L≤R≤n计算:如果事先已知了可怜只会在[L,R]这个区间的亭子里面玩耍,那么最少需要多少个保镖,才能让[L,R]中的每一个亭子都被监视到。由于输出量太大,可怜的父亲只要你输出所有[L,R]的答案的异或即可。

样例输入

3
2 3 1

样例输出

3

提示

如果R−L+1≤2,那么答案显然是1。
如果L=1,R=n,那么答案是2,需要安排两个保镖在(2,3),(3,1)两个位置监视可怜。
对于30%的数据,n≤20。
对于70%的数据,n≤500。
对于100%的数据,n≤5000。
对于100%的数据,1≤hi≤109。

来源/分类

江西OI2018 

题解:以前没接触过区间DP的我看着就懵逼了,太菜了。

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

猜你喜欢

转载自blog.csdn.net/sudu6666/article/details/81326632
今日推荐