洛谷P4563 [JXOI2018]守卫

48 篇文章 0 订阅

题目背景

九条可怜是一个热爱运动的女孩子。

题目描述

这一天她去爬山,她的父亲为了她的安全,雇了一些保镖,让他们固定地呆在在山的某些位置,来实时监视九条可怜,从而保护她。

具体来说,一座山可以描述为一条折线,折线的下方是岩石。这条折线有 nn 个折点,每个折点上有一个亭子,第 ii 个折点的坐标是 (i,h_i)(i,hi) 。九条可怜只可能会在亭子处玩耍,那些保镖也只会在亭子处监视可怜。

由于技术方面的原因,一个保镖只能监视所有他能看得到的,横坐标不超过他所在位置的亭子。我们称一个保镖能看到一个亭子 pp ,当且仅当他所在的亭子 qq 和 pp 的连线不经过任何一块岩石。特别地,如果这条连线恰好经过了除了 p,qp,q 以外的亭子,那么我们认为保镖看不到可怜。

雇佣保镖是一件很费钱的事情,可怜的父亲希望保镖越少越好。

可怜的父亲还希望得到详尽的雇佣保镖的方案,他知道有些亭子可能正在维修,他想对所有的 1\leq l\leq r\leq n1lrn计算:如果事先已知了只有区间 [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 n1lrn 计算:如果事先已知了可怜只会在 [l,r][l,r] 这个区间的亭子里面玩耍,那么最少需要多少个保镖,才能让 $[l,r] 中的每一个亭子都被监视到。由于输出量太大,可怜的父亲只要你输出所有$ [l,r]$的答案的异或即可。

输入输出样例

输入样例#1:  复制
3
2 3 1
输出样例#1:  复制
3

说明

样例解释

如果 r-l+1\leq 2rl+12 ,那么答案显然是 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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值