upc 6888: 守卫(区间dp O(n^2) )

6888: 守卫

时间限制: 1 Sec  内存限制: 512 MB
提交: 102  解决: 33
[提交] [状态] [讨论版] [命题人: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[i][j]表示区间 [ i, j ]的最优解。

首先能确定的是:右端点必定安排一名守卫。

枚举到 i 时,如果可以被 j 点看到,那么dp[ i ][ j ] = dp[ i+1 ][ j ];

如果 i 不能被 j 点看到, 那必定有点挡住了,不妨设最早出现的挡住i的点是pre,那么可以确定pre可以被j看到,不然pre没有能力挡住j看i。

那么dp[i][j]的最优解来自考虑[ i,pre]或[i,pre-1],分别表示pre-1处必定有一守卫或pre处必定有一守卫(因为pre到j之间已经解出来了,所以考虑一下i到pre-1就好了。但是,虽然pre可以被j看到,但如果这里放一个守卫的话,是有可能从pre看到 j无法看到的很多点。而在pre-1处,不知道此处多高,可能不如高度更高的pre点看到的多,从而节省了守卫数量)

考虑优化,pre点可以随时更新,当i点能被j看到时,更新pre,所以pre点一定能被j看到,我只需要累计一下从i+1到上一个点之间的最优值。同样这里要考虑pre-1和pre,原理同上一段。

【代码】

#include
using namespace std;
typedef long long ll;

int n, dp[5050][5050],h[5050];
ll cross(int p,int i,int j)
{
    return 1ll*(i-p)*(h[j]-h[p]) - 1ll*(j-p)*(h[i]-h[p]);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&h[i]);
    ll ans=0;
    for(int j=1;j<=n;j++) //枚举右端点
    {
        dp[j][j]=1;
        ans^=1;
        int pre=j,suffx=1; //suffx始终表示从pre点到j点的最优值
        for(int i=j-1;i>=1;i--)
        {
            if(pre==j||cross(i,pre,j)>0) //pre点挡不住i点,pre点可以前移
            {
                suffx+=min(dp[i+1][pre],dp[i+1][pre-1]); //点可以用j点看到,所以无需考虑
                pre=i;
            }
            dp[i][j]=suffx+min(dp[i][pre],dp[i][pre-1]); //考虑如果pre距i很远,这之间的守卫要考虑上
            ans^=dp[i][j];
        }
    }
    printf("%lld\n",ans);
    return 0;
}

 

你可能感兴趣的:(ACM**动态规划**)