【基于贪心的动态规划】【NOI2006】千年虫

千年虫是远古时代的生物,时隔几千万年,千年虫早已从地球上销声匿迹,人们对其知之甚少。考古生物学家最近开始对其有了兴趣,因为一批珍贵的千年虫化石被发现,这些化石保留了千年虫近乎完整的形态。理论科学家们根据这些化石归纳出了千年虫的一般形态特征模型,并且据此判定出千年虫就是蜈蚣的祖先!但科学家J发现了实际与理论的一些出入,他仔细的研究了上百个千年虫化石,发现其中大部分千年虫的形态都不完全符合理论模型,这到底是什么因素造成的呢?理论科学家K敏锐的指出,千年虫的形态保存在化石中很有可能发生各种变化,即便最细微的变化也能导致它不符合模型。于是,摆在科学家面前的新问题诞生了:判断一个化石中的千年虫与理论模型的差距有多大?具体来说,就是根据一个千年虫化石的形态A,找到一个符合理论模型的形态B,使 得B是最有可能在形成化石时变成形态A。 

理论学家提出的“千年虫形态特征模型”如下(如上图所示):躯体由头、尾、躯干、足四大部分构成。 1.头,尾用一对平行线段表示。称平行于头、尾的方向为x方向;垂直于x的方向为y方向; 2.在头尾之间有两条互不相交的折线段相连,他们与头、尾两条线段一起围成的区域称为躯干,两条折线段都满足以下条件:拐角均为钝角或者平角,且包含奇数条线段,从上往下数的奇数条垂直于x方向。 3.每条折线段从上往下数的第偶数条线段的躯干的另一侧长出一条足,即一个上、下底平行于x方向的梯形或矩形,且其中远离躯干一侧的边垂直于x方向。注意:足不能退化成三角形(即底边的长度均大于零),躯干两侧足的数目可以不一样。(如下图,左边有4条足,右边有5条足) 

可见,x-y直角坐标系内,躯干和所有足组成的实心区域的边界均平行或垂直于坐标轴。为了方便,我们假设所有这些边界的长度均为正整数。因此可以认为每个千年虫的躯体都由一些单位方格拼成。每个单位方格都由坐标(x,y)唯一确定。设头尾之间的距离为n,则我们可以用2×n个整数来描述一条千年虫B(如右图):将B沿平行x轴方向剖分成n条宽度为1的横条,每个横条最左边一格的x坐标设为Li,最右一格的的x坐标设为Ri。则(n,L1,L2,..,Ln,R1,R2,..Rn)就确定了一条千年虫。由于岁月的侵蚀,在实际发现的化石中,千年虫的形状并不满足上面理论模型的规则,一些格子中的躯体已经被某些矿物质溶解腐蚀了。地质、物理、生物学家共同研究得出: 1、腐蚀是以格子为单位的,只能一整格被腐蚀; 2、腐蚀是分步进行的,每一步只有一格被腐蚀; 3、如果去掉一个格子后躯体不连通了,那么这个格子当前不会被腐蚀; 4、如果一个格子的左边邻格和右边邻格都还没被腐蚀,那么这个格子当前不会被腐蚀; 5、与头相邻的格子不能全部被腐蚀,与尾相邻的格子不能全部被腐蚀;倘若满足上面五条,我们仍然可以用(n,L’1,L’2,..,L’n,R’1,R’2,..R’n)来描述一个化石里头的千年虫的形态。其中L’i≤R’i。例如下图: 

现在你的任务是,输入一个化石里的千年虫的描述A,找一个满足理论模型的千年虫的描述B,使得B可以通过腐蚀过程得以变为A,且由B转化为A的代价(须被腐蚀的格子数)最少。输出此最小代价。
Input
第一行为一个整数n;以下n行,每行两个整数,其中第i行为两个整数L’i,R’i,用一个空格分开;保证输入数据合法。
Output
仅一行,为一个整数,表示最少代价。
Sample Input
7
4 4
3 4
3 5
1 3
2 2
2 4
3 3
Sample Output
3
HINT

30%的数据 n≤100, 0≤L’i≤R’i≤100
50%的数据 n≤1000, 0≤L’i≤R’i≤1000
70%的数据 n≤100000, 0≤L’i≤R’i≤1000
100%的数据 n≤1000000, 0≤L’i≤R’i≤1000000
此题十分考察选手的语文素养……

题目大意是求出最少加多少格子使得图形的两边变成“梳子”形。

那么显然可以看出左右两边相对独立,于是可以单独计算。
设第i行的初始高度为a[i],最终高度为b[i],f[i][j][s]表示前i行中当前行最终高度为j,凹凸性为s(0凹1凸)所需要加的最少块数。
则有:
f[i][j][0] = min{f[i - 1][k][1], f[i - 1][j][0]} + j - a[i] (k > j)
f[i][j][1] = min{f[i - 1][k][0], f[i - 1][j][1]} + j - a[i] (k < j)
代码:

/***************************\
 * @prob: NOI2006 worm     *
 * @auth: Wang Junji       *
 * @stat: TLE: 30          *
 * @date: June. 1st, 2012  *
 * @memo: 动态规划          *
\***************************/
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>

const int maxN = 500010, INF = 0x3f3f3f3f;
int f[2][maxN][2], L[maxN], R[maxN], Now[maxN], n, Lim;

inline int calc()
{
    int pst = 0, ths = 1; memset(f[ths], 0x3f, sizeof f[ths]); ++Lim;
    for (int j = Now[1]; j < Lim + 1; ++j) f[ths][j][0] = j - Now[1];
    for (int i = 2; i < n + 1; ++i)
    {
        pst ^= 1, ths ^= 1; memset(f[ths], 0x3f, sizeof f[ths]);
        for (int j = Now[i]; j < Lim + 1; ++j)
        {
            int &f0 = f[ths][j][0], &f1 = f[ths][j][1];
            f0 = f[pst][j][0], f1 = f[pst][j][1];
            for (int k = Now[i - 1]; k < j; ++k) f1 = std::min(f1, f[pst][k][0]);
            for (int k = Lim; k > j; --k) f0 = std::min(f0, f[pst][k][1]);
            f0 += j - Now[i], f1 += j - Now[i];
        }
    }
    int Min = INF;
    for (int j = Now[n]; j < Lim + 1; ++j) Min = std::min(Min, f[ths][j][0]);
    return Min;
}

int main()
{
    freopen("worm.in", "r", stdin);
    freopen("worm.out", "w", stdout);
    scanf("%d", &n); int max_L = 0, min_R = INF;
    for (int i = 1; i < n + 1; ++i)
        scanf("%d%d", L + i, R + i),
        max_L = std::max(max_L, L[i]),
        min_R = std::min(min_R, R[i]);
    Lim = 0;
    for (int i = 1; i < n + 1; ++i) Lim = std::max(Lim, Now[i] = R[i] - min_R);
    int ans = calc(); Lim = 0;
    for (int i = 1; i < n + 1; ++i) Lim = std::max(Lim, Now[i] = max_L - L[i]);
    printf("%d\n", ans += calc()); return 0;
}

直接朴素计算显然要超时。

先加一个小小的优化。
用单调队列的思想可以将方程的转移优化到O(1)(实际上不需要队列,利用单调性就可以了)。

/***************************\
 * @prob: NOI2006 worm     *
 * @auth: Wang Junji       *
 * @stat: TLE: 40          *
 * @date: June. 1st, 2012  *
 * memo: 动态规划           *
\***************************/
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>

const int maxN = 60010, INF = 0x3f3f3f3f;
int f[2][maxN][2], L[maxN], R[maxN], Now[maxN], n, Lim;

inline int calc()
{
    int pst = 0, ths = 1; memset(f[ths], 0x3f, sizeof f[ths]); ++Lim;
    for (int j = Now[1]; j < Lim + 1; ++j) f[ths][j][0] = j - Now[1];
    for (int i = 2; i < n + 1; ++i)
    {
        pst ^= 1, ths ^= 1; int best0 = INF, best1 = INF;
        for (int j = 0; j < Now[i]; ++j) f[ths][j][0] = f[ths][j][1] = INF;
        for (int j = Now[i - 1]; j < Now[i]; ++j)
            best0 = std::min(best0, f[pst][j][0]);
        for (int j = Now[i]; j < Lim + 1; ++j)
            f[ths][j][1] = std::min(f[pst][j][1], best0) + j - Now[i],
            j >= Now[i - 1] ? (best0 = std::min(best0, f[pst][j][0])) : 0;
        for (int j = Lim; j > Now[i] - 1; --j)
            f[ths][j][0] = std::min(f[pst][j][0], best1) + j - Now[i],
            best1 = std::min(best1, f[pst][j][1]);
    }
    int Min = INF;
    for (int j = Now[n]; j < Lim + 1; ++j) Min = std::min(Min, f[ths][j][0]);
    return Min;
}

int main()
{
    freopen("worm.in", "r", stdin);
    freopen("worm.out", "w", stdout);
    scanf("%d", &n); int max_L = 0, min_R = INF;
    for (int i = 1; i < n + 1; ++i)
        scanf("%d%d", L + i, R + i),
        max_L = std::max(max_L, L[i]),
        min_R = std::min(min_R, R[i]);
    Lim = 0;
    for (int i = 1; i < n + 1; ++i) Lim = std::max(Lim, Now[i] = R[i] - min_R);
    int ans = calc(); Lim = 0;
    for (int i = 1; i < n + 1; ++i) Lim = std::max(Lim, Now[i] = max_L - L[i]);
    printf("%d\n", ans += calc()); return 0;
}

然后就看题解说第i行的最终高度的取值范围是
[a[k], a[k] + 3) (|k - i| < 3).(将这几个区间并起来)
(由于能力有限,无法对此进行证明。)
那么,有了这个结论,就可以优化到线性复杂度了。
代码:

/******************************\
 * @prob: NOI2006 worm        *
 * @auth: Wang Junji          *
 * @stat: Accepted.           *
 * @date: June. 2nd, 2012     *
 * @memo: 基于贪心的动态规划     *
\******************************/
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <cstring>
#include <string>
using std::min; using std::max;

const int maxN = 100010, INF = 0x3f3f3f3f;
struct Node
{
    int cnt, pos[20]; int &operator[](int Ind) {return pos[Ind];}
} node[2]; int f[2][20][2], L[maxN], R[maxN], Now[maxN], n;

inline int getint()
{
    int res = 0; char tmp;
    while (!isdigit(tmp = getchar()));
    do res = (res << 3) + (res << 1) + tmp - '0';
    while (isdigit(tmp = getchar()));
    return res;
}

inline void update(int &a, int b) {if (b < a) a = b; return;}

inline static int calc()
{
    int pst = 0, ths = 1; node[ths].cnt = 0;
    for (int j = 0; j < 3; ++j)
    for (int k = Now[j]; k < Now[j] + 3; ++k)
        if (k >= Now[0]) node[ths][node[ths].cnt++] = k;
    //得到需要被计算的几个区间。
    for (int j = 0; j < node[ths].cnt; ++j)
        f[ths][j][0] = node[ths][j] - Now[0], f[ths][j][1] = INF;
    for (int i = 1; i < n; ++i)
    {
        ths ^= 1, pst ^= 1; node[ths].cnt = 0;
        for (int j = max(i - 2, 0); j < i + 3 && j < n; ++j)
        for (int k = Now[j]; k < Now[j] + 3; ++k)
            if (k >= Now[i]) node[ths][node[ths].cnt++] = k;
        for (int j = 0; j < node[ths].cnt; ++j)
        {
            f[ths][j][0] = f[ths][j][1] = INF;
            for (int k = 0; k < node[pst].cnt; ++k)
            {
                if (node[pst][k] > node[ths][j]) update(f[ths][j][0], f[pst][k][1]);
                else if (node[pst][k] < node[ths][j]) update(f[ths][j][1], f[pst][k][0]);
                else update(f[ths][j][0], f[pst][k][0]), update(f[ths][j][1], f[pst][k][1]);
            }
            f[ths][j][0] += node[ths][j] - Now[i], f[ths][j][1] += node[ths][j] - Now[i];
        }
    }
    int Min = INF;
    for (int j = 0; j < node[ths].cnt; ++j) update(Min, f[ths][j][0]);
    return Min;
}

int main()
{
    freopen("worm.in", "r", stdin);
    freopen("worm.out", "w", stdout);
    n = getint();
    for (int i = 0; i < n; ++i) L[i] = getint(), Now[i] = getint();
    int ans = calc(); 
    for (int i = 0; i < n; ++i) Now[i] = maxN - L[i];
    printf("%d\n", ans += calc()); return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值