洛谷 P4563 [JXOI2018] (Upc 6888)守卫 这辈子不可能学会的动态规划之区间DP

题目链接:https://www.luogu.org/problemnew/show/P4563

题目描述

九条可怜是一个热爱运动的女孩子。 这一天她去爬山,她的父亲为了她的安全,雇了一些保镖,让他们固定地呆在在山的某些位置,来实时监视九条可怜,从而保护她。 具体来说,一座山可以描述为一条折线,折线的下方是岩石。这条折线有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 

为什么你们都会动态规划呀,为什么你们啥都会呀,就我啥都不会,菜的不行。

题意+思维:

我动态规划是完全不会,这个题是看别人博客思想写的,题解借鉴以下博客:https://www.cnblogs.com/Alan-Luo/articles/9075517.html

由题意中描述,我们可以得到这样一幅图

  这幅图给的是九条爬山的路径,我们可以看到,从一些坐标可以看到,有的点可以看到他坐标以前以下的点,但有的点不能,这是本题的一个创新之处,但是大部分人可以想到做法,只是不敢相信这就是正解!没错,就是判斜率,用最简单的数学方法判断一个点能不能挡住另一个点的视线!

  这样这道题其实就完成一半了,剩下的我们可以做一个区间dp,这个区间dp不像之前我们见过的,枚举区间长度一 一进行转移;而是采用线性dp的方法枚举左右端点即可,但是左端点要从右端点往前枚举;(其实枚举区间长度应该也没问题,各位大神可以试一下);

  所以我们定 f[l][r]代表l到r间的最优解,我们可以找到所有能被上面监视的点然后枚举是否安插一个保镖,以此进行转移! 

以下是个人从狗屁不懂到问了别人慢慢醒悟中理解的:

对于L到R的区间来说,对于这一段结果有影响的是从顶点开始到所有可达的点的影响,进而可以转换成将整个区间分割成好多块,然后每一块求出来最优然后就是对于当前这个大的区间的一种最优的情况。因为加入顶点可以看到某个点就可以考虑那个点是否需要一定放一个人来守卫,这样就不断优化区间,dp[j+1][l-1] 就是考虑那种可以不放守卫,顶点也可以来看到当前点的可以使减少一个放守卫的人数。

区间DP,顾名思义是在区间上DP,它的主要思想就是先在小区间进行DP得到最优解,然后再利用小区间的最优解合并求大区间的最优解。最重要的是没有后效性。

所以对于当前题目就是从前往后不断求出来1-L的最优解,然后对于后边直接就考虑X-r的最优解就行了,自己画画图好好理解下,区间DP推荐博客学习:https://blog.csdn.net/my_sunshine26/article/details/77141398  (虽然我已经看DP 看到已经自闭)

#include<bits/stdc++.h>
using namespace std;
int a[5005];
int dp[5005][5005];
int check(int l,int mid,int r)//比较斜率 
{
	return (a[r]-a[mid])/(r-mid) > (a[r]-a[l])/(r-l);
} 
int main() 
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	int ans=0; 
	for(int i=1;i<=n;i++)
	{
		dp[i][i]=1;//对于任何L-r的区间 r肯定要放一个 
		int sum=1;//表示当前 l-r 需要的保镖的最小数量 
		int l=i; //当前最远点 
		ans = ans ^ dp[i][i];//计算 
		for(int j=i-1;j>=1;j--)
		{
			if(l==i || check(j,l,i))//如果是第一个点 或者是 挡不住 
			{ 
				sum = sum + min(dp[j+1][l-1],dp[j+1][l]);//考虑不放或者放 取一个最小值 
				l=j;//不断更新分割区间的那个点 
			}//对于j--i的区间得考虑j端点 
			dp[j][i] = sum + min(dp[j][l-1],dp[j][l]);//如果借助看不到当前点就考虑那个点到l之间的最优解 因为l之后已经是最优的解 
			ans=ans^dp[j][i];//对于每个区间都进行^运算 
		}
	}
	printf("%d\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/passer__/article/details/81434502