ZOJ 3650 Toy Blocks(DP + 线段树优化转移)

题目链接:Click here~~

题意:

给出 n 个多米诺骨牌的 X 坐标以及高度 H,每次你可以选任意一张向左推或向右推,推倒后,会产生连锁反应,问最少几次能把所有的骨牌推倒。

解题思路:

先预处理出每张牌向 左/右 推能推到的最远位置,记为 l[i] 和 r[i]。这步可以通过递推的方法在均摊总复杂度为 O(n) 下得到。

然后 dp[i][dir] 表示前 i 张牌推倒 && 第 i 张牌倒的方向为 dir 的最少次数。

如果循环方向为从 1->n 的话,dp[i][0] = min(dp[i][l[i]-1]) + 1。这个应该不难想出来,因为 dp[i-1][dir] <= dp[i][dir] ,所以一定能推越远越好。

dp[i][1] = min ( min{dp[j][1]} , min(dp[i-1]) + 1 ) (j < i && r[j] >= i)。

即,如果向右倒的话,考虑是别人推倒自己,还是自己推倒自己。{ }内的部分要用线段树优化一下,使转移的复杂度为 O(logn)。

好像存在O(1)的转移写法,但是看不懂。还有就是{ }内的部分可以用树状数组来实现,也暂时不会。mark 一下。

#include <stdio.h>
#include <string.h>
#include <algorithm>

using namespace std;

const int N = 1e5 + 3;

pair<int,int> a[N];

int l[N],r[N],dp[N][2];

void pre(int n)
{
    for(int i=1;i<=n;i++)
        l[i] = r[i] = i;
    for(int i=2;i<=n;i++)
    {
        int k = i;
        while(l[k] != 1  && a[i].first-a[l[k]-1].first <= a[i].second)
            k = l[k] - 1;
        l[i] = l[k];
    }
    for(int i=n-1;i>=1;i--)
    {
        int k = i;
        while(r[k] != n  && a[r[k]+1].first-a[i].first <= a[i].second)
            k = r[k] + 1;
        r[i] = r[k];
    }
}

inline void check_min(int &a,int b){
    a = a < b ? a : b;
}

#define lson u<<1
#define rson u<<1|1

struct Seg
{
    int l,r;
    int mmin,lazy;
    inline int mid(){
        return l + r >> 1;
    }
}T[N<<2];

void build(int u,int l,int r)
{
    T[u].l = l , T[u].r = r;
    T[u].mmin = T[u].lazy = N;
    if(l == r - 1)
        return ;
    int m = T[u].mid();
    build(lson,l,m);
    build(rson,m,r);
}

void push_up(int u)
{
    T[u].mmin = min(T[lson].mmin,T[rson].mmin);
}

void push_down(int u)
{
    if(T[u].lazy == N)
        return ;
    check_min(T[lson].mmin,T[u].lazy);
    check_min(T[rson].mmin,T[u].lazy);
    check_min(T[lson].lazy,T[u].lazy);
    check_min(T[rson].lazy,T[u].lazy);
    T[u].lazy = N;
}

void updata(int u,int l,int r,int up)
{
    if(T[u].l == l && T[u].r == r)
    {
        check_min(T[u].mmin,up);
        check_min(T[u].lazy,up);
        return ;
    }
    push_down(u);
    int m = T[u].mid();
    if(r <= m)
        updata(lson,l,r,up);
    else if(l >= m)
        updata(rson,l,r,up);
    else
        updata(lson,l,m,up) , updata(rson,m,r,up);
    push_up(u);
}

int query(int u,int l)
{
    if(T[u].l == l && T[u].r == l+1)
        return T[u].mmin;
    push_down(u);
    int m = T[u].mid();
    if(l <  m)
        return query(lson,l);
    else
        return query(rson,l);
}

int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        for(int i=1;i<=n;i++)
            scanf("%d%d",&a[i].first,&a[i].second);
        sort(a+1,a+1+n);
        pre(n);
        build(1,1,n+1);
        dp[0][0] = dp[0][1] = 0;
        for(int i=1;i<=n;i++)
        {
            dp[i][0] = min(dp[l[i]-1][0],dp[l[i]-1][1]) + 1;
            dp[i][1] = min(dp[i-1][0],dp[i-1][1]) + 1;
            check_min(dp[i][1],query(1,i));
            updata(1,i,r[i]+1,dp[i][1]);
        }
        printf("%d\n",min(dp[n][0],dp[n][1]));
    }
    return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值