ZOJ 3650(多米诺骨牌 dp + 线段树优化)

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3650

题意: 给你n个骨牌,每个骨牌有一个在x轴上的位置和高度,每个骨牌可以想左推也可以向右推,问最少多少步可以把骨牌全部推倒。

思路:

之前Goodbye 2014 上的E题就是一道多米诺骨牌的题目,虽然跟这道题目不太一样但是还是提供了一下思路, 附题解的链接:

http://blog.csdn.net/u013649253/article/details/42360503

建立在之前那题题目的基础上,这道题目我还是前后做了三个小时才最后a掉,总的来说这道题目就是:细节多,码量大,转移复杂。

dp的定义:

dp[i][0] 表示把前1 ~ i个骨牌全部推倒, 且第i个向右倒的最小步数, dp[i][1]表示把前1 ~ i个骨牌全部推倒,且第i个向左倒的最小步数。

-----------

dp[i][0]的转移考虑两种情况, 一种是被它左边的骨牌推倒,第二种情况是自己用手把它推倒。

对于第一种转移情况,有dp[i][0] = min(dp[i][0], dp[j][0]) (j为满足条件 x[j] + h[j] >= x[i] && j < i ),所有满足条件的j快速的找出来并不简单, 所有我们可以换一种思路就是利用刷表法的思路,再计算出j的时候把区间x[j] ~ x[j] + h[j]上通过区间赋值都附成dp[j][0],这样在更新到dp[i][0]的时候已经满足了第一种转移情况的最小值了。

对于第二种转移情况,有dp[i][0] = min(dp[i][0], min(dp[i-1][0], dp[i-1][1]) + 1) 这就相当于取将前1~i-1个全部推倒的最小值再加1。

转移完后需要再用dp[i][0] 来更新 i ~ kr 区间上的值(kr为i骨牌向右能达到的最远的骨牌)。 此处用到的相当于是线段树的区间更新(区间赋值), 单点查询(最小值),需要维护一个域和一个懒标记。

-----------

dp[i][1]的转移也有两种情况, 记kl为第i个骨牌向左能达到的最远的骨牌的标号,则

dp[i][1] = min (dp[i][1], min(dp[kl-1][1], dp[kl-1][1]) + 1)

dp[i][1] = min (dp[i][1], dp[j][1]) ( kl <= j <= i) 此处不用+1, 虽然是用手推的但是,kl ~ i的骨牌一定是一起倒的(只用手推一次),其转移的状态一定是先经过第一种转移的,所以此处不+1,这个地方我一开始都没有注意到。

所以此处的转移,用线段树维护一个单点更新 区间查询即可。

还有一些其他的细节就是,

1. 给出的骨牌不是按x的从小到大给出的自己一开始要排序。

2. 每个位置上的骨牌数量有多个,应该取最高的那个。

我是用 map + 离散化 处理的~~~。

实现的时候建了两棵线段树, 码量略大~~~~

code:

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <map>
#define clr(x, y) memset(x, y, sizeof x)
#define inf 1000000000
using namespace std;

const int maxn = 100005;

typedef long long LL;

LL xi[maxn];
LL hi[maxn];
// pl向左最远编号
// pr向右最远编号
int pl[maxn], pr[maxn];
int n;
// va means dp[i][0]
// vb means dp[i][1]
int va[maxn<<2], col[maxn<<2];
int vb[maxn<<2];
int ma[maxn], mb[maxn];
map<LL, LL> mi;

//-------
void pushup_va(int rt)
{
    va[rt] = min(va[rt<<1], va[rt<<1|1]);
}
void pushdown_va(int rt)
{
    col[rt<<1] = col[rt<<1|1] = col[rt];
    va[rt<<1] = min(va[rt<<1], col[rt<<1]);
    va[rt<<1|1] = min(va[rt<<1|1], col[rt<<1|1]);
    col[rt] = -1;
}
void update_va(int L, int R, int c, int l, int r, int rt)
{
    if (L <= l && r <= R)
    {
        col[rt] = c;
        va[rt] = min(va[rt], col[rt]);
        return ;
    }
    if (col[rt] != -1) pushdown_va(rt);
    int m = (l + r)>>1;
    if (L <= m) update_va(L, R, c, l, m, rt<<1);
    if (R > m) update_va(L, R, c, m+1, r, rt<<1|1);
    pushup_va(rt);
}
int query_va(int p, int l, int r, int rt)
{
    if (l == r)
    {
        //printf("p=%d va=%d col=%d\n",p,va[rt],col[rt]);
        return va[rt];
    }

    if (col[rt] != -1) pushdown_va(rt); //--*
    int m = (l + r)>>1;
    if (p <= m) return query_va(p, l, m, rt<<1);
    else return query_va(p, m+1, r, rt<<1|1);
}
//-------
void pushup_vb(int rt)
{
    vb[rt] = min(vb[rt<<1], vb[rt<<1|1]);
}
void update_vb(int p, int c, int l, int r, int rt)
{
    if (l == r)
    {
        vb[rt] = c;
        return ;
    }
    int m = (l + r) >> 1;
    if (p <= m) update_vb(p, c, l, m, rt<<1);
    else update_vb(p, c, m+1, r, rt<<1|1);
    pushup_vb(rt);
}
int query_vb(int L, int R, int l, int r, int rt)
{
    if (L <= l && r <= R) return vb[rt];

    int m = (l + r) >> 1;
    int ans = inf;
    if (L <= m) ans = min(ans, query_vb(L, R, l, m, rt<<1));
    if (R > m) ans = min(ans, query_vb(L, R, m + 1, r, rt<<1|1));
    return ans;
}

void build(int l, int r, int rt)
{
    va[rt] = vb[rt] = inf;
    col[rt] = -1;
    if (l == r) return ;
    int m = (l + r) >> 1;
    build(l, m, rt<<1);
    build(m+1, r, rt<<1|1);
    pushup_va(rt);
    pushup_vb(rt);
}

int main()
{
    //freopen("input.txt", "r", stdin);

    while(scanf("%d", &n) == 1)
    {
        mi.clear();
        for (int i = 1; i <= n; i++)
        {
            scanf("%lld%lld", &xi[i], &hi[i]);
            if(mi.count(xi[i]) == 0)
                mi[xi[i]] = hi[i];
            else
                mi[xi[i]] = max(mi[xi[i]], hi[i]);
        }
        xi[0] = -1;
        sort(xi, xi + n + 1);
        int len = unique(xi, xi + n + 1) - xi;
        n = len - 1;

        for (int i = 1; i <= n; i++)
        {
            hi[i] = mi[xi[i]];
            pl[i] = lower_bound(xi+1, xi + n + 1, xi[i] - hi[i]) - xi;
            pr[i] = upper_bound(xi+1, xi + n + 1, xi[i] + hi[i]) - xi - 1;
        }
       // for (int i = 1; i <= n; i++) printf("i=%d xi=%d hi=%d pl=%d pr=%d\n", i, xi[i], hi[i], pl[i], pr[i]);
        build(1, n, 1);
        clr(ma, 0); clr(mb, 0);

        for (int i = 1; i <= n; i++)
        {
            int da = inf;
            int db = inf;
            db = min(db, query_vb(pl[i], i, 1, n, 1));

            int mid = min(ma[pl[i] - 1], mb[pl[i] - 1]);
            db = min (db, mid + 1);

            da = min(da, query_va(i, 1, n, 1));
            //cout<<"i="<<i<<"da="<<da<<endl;
            da = min(da, mb[i - 1] + 1);
            da = min(da, ma[i - 1] + 1);
            //cout<<"i="<<i<<"da="<<da<<endl;

            ma[i] = da; mb[i] = db;
            update_va(i, pr[i], da, 1, n, 1);
            update_vb(i, db, 1, n, 1);
        }
        //for (int i = 1; i <= n; i++) printf("i = %d ma = %d mb = %d\n", i, ma[i], mb[i]);

        printf("%d\n", min(ma[n], mb[n]));
    }
    return 0;
}

加油~~

时间: 2024-08-08 00:36:52

ZOJ 3650(多米诺骨牌 dp + 线段树优化)的相关文章

题解 HDU 3698 Let the light guide us Dp + 线段树优化

http://acm.hdu.edu.cn/showproblem.php?pid=3698 Let the light guide us Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 62768/32768 K (Java/Others) Total Submission(s): 759    Accepted Submission(s): 253 Problem Description Plain of despair was

HDU4719-Oh My Holy FFF(DP线段树优化)

Oh My Holy FFF Time Limit: 5000/2500 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others) Total Submission(s): 606    Accepted Submission(s): 141 Problem Description N soldiers from the famous "*FFF* army" is standing in a line, from le

hdu 4719 Oh My Holy FFF(dp线段树优化)

Oh My Holy FFF Time Limit: 5000/2500 MS (Java/Others)    Memory Limit: 65535/65535 K (Java/Others) Total Submission(s): 848    Accepted Submission(s): 219 Problem Description N soldiers from the famous "*FFF* army" is standing in a line, from le

hdu3698 Let the light guide us dp+线段树优化

http://acm.hdu.edu.cn/showproblem.php?pid=3698 Let the light guide us Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 62768/32768 K (Java/Others) Total Submission(s): 821    Accepted Submission(s): 285 Problem Description Plain of despair was

【uva1502/hdu4117-GRE Words】DP+线段树优化+AC自动机

这题我的代码在hdu上AC,在uva上WA. 题意:按顺序输入n个串以及它的权值di,要求在其中选取一些串,前一个必须是后一个的子串.问d值的和最大是多少. (1≤n≤2×10^4 ,串的总长度<=3*10^5) 题解: 这题一开始我的方向就错了,想了很久d[x][y]表示在AC自动机上的节点x.下一个串要大于y的dp.然而这样做数组要10^4*10^5=10^9级别,开都开不了,妥妥超时. 后来看了一眼题解...觉得自己智商真是感人... 用f[i]表示以第i个串为结尾的时候最大的d值,这样做

hdu 3450 离散化+dp+线段树优化

链接:http://acm.hdu.edu.cn/showproblem.php?pid=3450 题意: 给你一串长度为n的序列,给一个d,要求找出有几个子序列能够满足两个相邻的元素之间差值不超过d. 思路: dp.定义dp[i]表示以第i个为结束的满足条件的子序列的个数. 转移方程:dp[i]=(∑i?1j=1dp[j])+1(abs(num[i]?num[j])<=d) 答案就是dp数组的总和最后扣掉n就可以了. 此时会发现更新的时间复杂度是O(n2),这个显然是过不了的. 转移的复杂度是

[后缀数组+dp/AC自动机+dp+线段树] hdu 4117 GRE Words

题意: 给你N个字符串, N(1 <= N <= 2w), 所有串的长度加一起不超过30w.每个串有个值.这个值[-1000, 1000]. 问不打乱字符串顺序,从中取若干个字符串,使得前一个串是后一个串的子串,求满足前面调条件的字符串值得和最大,求这个值. 思路: 其实就是一个很明显的dp. dp[i]代表以第i个字符串结尾的最大权值. 但是就是子串这个问题怎么处理. 由于这题数据比较水可以用后缀数组处理这个问题. 将所有字符串拼接,做sa. 每次在height数组里往上和往下寻找公共前缀等

洛谷 P1282 多米诺骨牌

题目描述 多米诺骨牌有上下2个方块组成,每个方块中有1~6个点.现有排成行的 上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|.例如在图8-1中,S1=6+1+1+1=9,S2=1+5+3+2=11,|S1-S2|=2.每个多米诺骨牌可以旋转180°,使得上下两个方块互换位置. 编程用最少的旋转次数使多米诺骨牌上下2行点数之差达到最小. 对于图中的例子,只要将最后一个多米诺骨牌旋转180°,可使上下2行点数之差为0. 输入输出格式 输入格式: 输入文件的第一行是一个正

状态压缩动态规划 -- 多米诺骨牌

用1*2 的骨牌通过组合拼成 m * n 的大矩形,问有几种拼法. 题目链接:http://poj.org/problem?id=2411 状态转移: 1.由于上一行的该列竖直放置骨牌为 0,影响到当前行的该列,当前行的该列为 1 2.当前行骨牌横放,上一行骨牌横放, 都为11 3.上一行该列置为 1,当前行当前列立着放为 0 #include <iostream> #include <cstring> using namespace std; #define MAXSIZE 12