习题:守卫(DP)

题目

传送门

思路

这道题的题面十分具有迷惑性。。。
某位大犇上来直接用凸包
但是仔细分析之后发现,有点像区间DP
对于\(l_0r_0\)\(l_1 r_1\)两个区间,如果这两个区间是不相交
那么这两个区间实际上是不会影响对方的DP值的
有了这个结论之后,我们设\(dp_{i j}\)为区间为i和j的最小值
转移方程即为\(dp_{ij}=1+\sum _{}dp_{l_{k}r_k}\)

50pts

一个时间复杂度为\(O(n^3)\)算法就出现了
前两层循环枚举左端点与右端点,
最内层的循环就是枚举之间看不到的区间
对于每一个看不到的区间可能在它的右端点放一个守卫可能更

100pts

我们考虑对50pts的算法的改进,发现最内层的k是根本不需要的
因为可以先固定右端点,
在枚举左端点的时候顺便就可以将看不见的统计出来

代码

#include<iostream>
#include<climits>
using namespace std;
#define int long long
int n;
int h[5005];
int ans=1;
int dp[5005][5005];//表示区间i,j的最小值
bool f[5005][5005];//表示i号节点和j号节点之间能否互相看到
double solve_slope(int a,int b)
{
	return 1.0*(h[a]-h[b])/(a-b);
}
signed main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>h[i];
	for(int i=2;i<=n;i++)
	{
		double now=-INT_MIN;
		for(int j=i-1;j>=1;j--)
		{
			double t=solve_slope(j,i);
			if(t>now)
			{
				f[j][i]=f[i][j]=1;
				now=t;
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
			cout<<f[i][j]<<" ";
		cout<<endl;
	}
	for(int i=1;i<=n;i++)
	{
		int lastt=0;
		int s=1;
		for(int j=i;j>=1;j--)
		{
			if(f[j][i])
			{
				if(!f[j+1][i])
				{
					s+=min(dp[j+1][lastt],dp[j+1][lastt+1]);
				}
				dp[j][i]=s;
			}
			else
			{
				if(f[j+1][i])
				{
					lastt=j;
				}
				dp[j][i]=s+min(dp[j][lastt+1],dp[j][lastt]);
			}
			ans^=dp[j][i];
		}
	}
	cout<<ans;
	return 0;
}
posted @ 2019-10-24 21:21  loney_s  阅读(153)  评论(0)    收藏  举报