【BZOJ5324】守卫(JXOI2018)-区间DP+优化

测试地址:守卫
做法:本题需要用到区间DP+优化。
看到数据范围,容易想到令f(l,r)为区间[l,r]的答案,我们来考虑怎么转移。
对于一个区间[l,r],首先点r是一定要有人的,对于点r,它能看到的所有点可以这样求:从点r1开始,如果它到点r的斜率和上一个能看到的点到点r的斜率相比更小,那么当前点就能看到,否则就看不到(可以把坐标系转换为以点r为极点的极坐标系来考虑),这样从右往左扫一遍就可以求出它能看到的所有点了。于是我们可以O(n2)预处理出这些信息。
那我们有了这些信息,再考虑每一个点r看不到的点的连续区间[lk,rk],注意到对于x>rk+1,点x都是不可能看到区间[lk,rk]中的点的,这个结论画画图也可以得出来。由这个结论我们可以得出,每个这样的区间的决策对整个区间[l,r]来说,贡献是独立的,而对于一个区间[lk,rk],要使得里面的点全部被看到,可以选择在点rk或点rk+1布置一个人,贡献分别为f(lk,rk)f(lk,rk+1),因此我们有状态转移方程:
f(l,r)=1+min(f(lk,rk),f(lk,rk+1))
这个方程是O(n3)的,显然不能通过此题,这就需要我们的优化。从方程本身的角度已经很难优化下去了,因此我们对求方程的方法进行优化。
注意到rk+1<r,因此我们从小到大枚举r来确保当前状态所需要的状态都已经被计算,而在r固定的情况下,l左移时,经过的r看不到的连续区间的贡献就可以顺便记录下来,这样我们就可以做到O(n2)的总时间复杂度了,于是我们就完成了这一题。
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
ll n,h[5010],f[5010][5010];
bool see[5010][5010];

int main()
{
    scanf("%lld",&n);
    for(ll i=1;i<=n;i++)
        scanf("%lld",&h[i]);

    for(ll i=1;i<=n;i++)
    {
        see[i][i]=0;
        for(ll j=i-1,last=0;j>=1;j--)
        {
            if (!last||(h[i]-h[j])*(i-last)<(h[i]-h[last])*(i-j))
            {
                see[i][j]=1;
                last=j;
            }
            else see[i][j]=0;
        }
    }

    ll ans=0;
    for(ll r=1;r<=n;r++)
    {
        ll lastans=1,lastr=0;
        for(ll l=r;l>=1;l--)
        {
            if (see[r][l])
            {
                if (lastr)
                {
                    lastans+=min(f[l+1][lastr],f[l+1][lastr+1]);
                    lastr=0;
                }
            }
            else
            {
                if (!lastr) lastr=l;
                f[l][r]+=min(f[l][lastr],f[l][lastr+1]);
            }
            f[l][r]+=lastans;
            ans^=f[l][r];
        }
    }
    printf("%lld",ans);

    return 0;
}
posted @ 2018-05-24 21:02  Maxwei_wzj  阅读(109)  评论(0编辑  收藏  举报