【基於貪心的動態規劃】【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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章