用于解决这一具有挑战性的动态编程任务的指针

时间:2016-09-14 12:12:36

标签: algorithm dynamic-programming

我一直在PEG Online Judge中遇到一个问题,名为Dominos,你可以在这里找到:

http://wcipeg.com/problem/domino

简要描述:

我们获得了一个水平排列的不同高度和位置的多米诺骨牌列表。 位置x的高度为h的多米诺骨牌,一旦被推向右侧,将在x + 1,x + 2,...,x + h位置向右敲击所有多米诺骨牌。 相反,向左推的同一个多米诺骨牌会将所有多米诺骨牌都击倒在x-1,x-2,...,x-h左侧。

推翻所有多米诺骨牌的最低推送次数是多少?

示例:

public function index() {  

   $empid = "12345";

   //running function / step / process / one 

   $this->function1($empid);

   //running function / step / process / two

   $this->function2($empid);

   $this->load->view("welcome",$data);

}

答案 2 。将多米诺骨牌推向位置1向右,将多米诺骨牌推向位置8向左。

约束:

  

输入以单个整数N≤100,000开头,数量为   多米诺骨牌,其次是N对整数。每对整数   代表多米诺骨牌的位置和高度。 (1≤位置≤   1,000,000,000,1≤身高≤1,000,000,000)没有两张多米诺骨牌   同一地点。

     

内存限制:64mb

     

时间限制:1.00s

     

注意:60%的测试数据的N≤5000。

有一种强力解决方案只能解决60%的输入问题。

看起来应该有一个使用动态编程的子二次或甚至线性解决方案,以便获得最大输入大小的AC。

任何提示都将不胜感激。

作者提示,我不太明白,如果有帮助的话:

  

创建一个递归函数f(n),它给出了最小的移动次数   需要推翻前n个多米诺骨牌。

     

现在,您如何将f(n)与f的先前值相关联? Domino #n有两个   选择:向左走(在这种情况下,它推翻其他多米诺骨牌)或去   对(在这种情况下,左边的另一个多米诺骨牌将其翻倒)。尝试   在那里工作。

谢谢!

3 个答案:

答案 0 :(得分:4)

以下是O(N log N)解决方案:

  1. 让我们弄清楚如果我们将i-th多米诺骨牌推向左边(让我们表示为L[i]),如何计算掉落的最左边的多米诺骨牌。一个人想到的第一个想法是运行简单的模拟。但那太慢了。我声称当我们从左向右迭代时,我们可以保持一堆“有趣的”多米诺骨牌索引。它的伪代码如下所示:

    s = Empty stack of dominos
    for i = 0 .. n - 1
        while we can knock s.top() domino by pushing the i-th to the left
            s.pop()
        the s.top() is the rightmost domino we cannot hit 
        (if s is empty, we can hit all of them)
        s.push(i-th domino)
    

    此代码以线性时间运行(每个多米诺骨牌只需按一次,最多弹出一次)。它可能看起来不是很直观(我不会在这里写一个完整的正式证明,因为它太长了),但手动处理小例子有助于理解它为什么是正确的。
    事实上,这种技术值得理解,因为它常用于竞争性编程(当某些东西从右向左移动时,我们需要找到最左边的元素,满足每个正确元素的某些条件。我知道它听起来有点模糊)。

  2. 我们可以在线性时间内以相同方式计算R[i](如果我们将i-th多米诺骨牌向右推)我们可以走多远。

  3. 现在我们知道如果我们选择向任何方向推送任何多米诺骨牌会发生什么。凉!

  4. 让我们使用动态编程来计算答案。让f(i)成为我们需要做的最少数量的操作,以便所有包含i-th的多米诺骨牌都被打倒,其余的仍未受影响。过渡是很自然的:我们要么将多米诺骨牌推向左边还是右边。在前一种情况下,我们进行了转化f(j) + 1 -> f(i),其中L[i] - 1 <= j < i。在后一种情况下,转换是f(i - 1) + 1 -> f(R[i])。此解决方案是正确的,因为它会尝试每个多米诺骨牌的所有可能操作。

  5. 如何使这部分高效?我们需要支持两个操作:更新点中的值并获得范围中的最小值。段树可以在O(log N)中处理它们。它为我们提供了O(N log N)解决方案。

  6. 如果这个解决方案看起来太难了,你可以先尝试实现一个更简单的解决方案:只需运行模拟来计算L[i]R[i],然后根据定义计算动态编程数组(不需要细分树)以便很好地理解这些问题在这个问题中意味着什么(应该得到60分)。完成后,您可以应用堆栈和分段树优化来获得完整的解决方案。

    如果某些细节不清楚,我会提供正确解决方案的实施方式,以便您可以在那里查找:

    #include <bits/stdc++.h>
    
    using namespace std;
    
    typedef pair<int, int> pii;
    
    vector<int> calcLeft(const vector<pii>& xs) {
        int n = xs.size();
        vector<int> res(n, 1);
        vector<int> prev;
        for (int i = 0; i < xs.size(); i++) {
            while (prev.size() > 0 && xs[prev.back()].first >= xs[i].first - xs[i].second)
                prev.pop_back();
            if (prev.size() > 0)
                res[i] = prev.back() + 2;        
            prev.push_back(i);
        }
        return res;
    }
    
    vector<int> calcRight(vector<pii> xs) {
        int n = xs.size();
        for (int i = 0; i < xs.size(); i++)
            xs[i].first = -xs[i].first;
        reverse(xs.begin(), xs.end());
        vector<int> l = calcLeft(xs);
        reverse(l.begin(), l.end());
        for (int i = 0; i < l.size(); i++)
            l[i] = n + 1 - l[i];
        return l;
    }
    
    const int INF = (int) 1e9;
    
    struct Tree {
    
        vector<int> t;
        int size;
    
        Tree(int size): size(size) {
            t.assign(4 * size + 10, INF);
        }
    
        void put(int i, int tl, int tr, int pos, int val) {
            t[i] = min(t[i], val);
            if (tl == tr)
                return;
            int m = (tl + tr) / 2;
            if (pos <= m)
                put(2 * i + 1, tl, m, pos, val);
            else
                put(2 * i + 2, m + 1, tr, pos, val);
        }
    
        void put(int pos, int val) {
            put(0, 0, size - 1, pos, val);
        }
    
        int get(int i, int tl, int tr, int l, int r) {
            if (l == tl && r == tr)
                return t[i];
            int m = (tl + tr) / 2;
            int minL = INF;
            int minR = INF;
            if (l <= m)
                minL = get(2 * i + 1, tl, m, l, min(r, m));
            if (r > m)
                minR = get(2 * i + 2, m + 1, tr, max(m + 1, l), r);
            return min(minL, minR);
        }
    
        int get(int l, int r) {
            return get(0, 0, size - 1, l, r);
        }
    };
    
    int getCover(vector<int> l, vector<int> r) {
        int n = l.size();
        Tree tree(n + 1);
        tree.put(0, 0);
        for (int i = 0; i < n; i++) {
            int x = i + 1;
            int low = l[i];
            int high = r[i];
            int cur = tree.get(x - 1, x - 1);
            int newVal = tree.get(low - 1, x - 1);
            tree.put(x, newVal + 1);
            tree.put(high, cur + 1);
        }
        return tree.get(n, n);
    }
    
    
    int main() {
        ios_base::sync_with_stdio(0);
        int n;
        cin >> n;
        vector<pii> xs(n);
        for (int i = 0; i < n; i++)
            cin >> xs[i].first >> xs[i].second;
        sort(xs.begin(), xs.end());
        vector<int> l = calcLeft(xs);
        vector<int> r = calcRight(xs);
        cout << getCover(l, r) << endl;
        return 0;
    }
    

答案 1 :(得分:1)

这个问题可以在没有segtree的O(N)中解决

正如kraskevich所提到的,我们需要找到L[i] - 1i - 1范围内的最小值。 我们可以保留一个有趣位置及其dp值的列表,其中位置和dp值都是按升序排列。

当我们想要查询范围中的最小值时,我们可以轻松地从后面扫描列表并找到该范围内的最小有趣点。

我们更新dp[x]后,我们会弹回列表中dp值大于dp[x]的所有点(因为这些点不再有趣),并将(x, dp[x])添加到列出一个新的有趣点。

这是在线性时间内运行的。

int getCover(vector<int> l, vector<int> r) {
    int n = l.size();
    vector<int> dp(n + 1, INF);
    dp[0] = 0;
    vector<pii> st;
    st.emplace_back(0, 0);
    for (int i = 0; i < n; i++) {
        int x = i + 1;
        int low = l[i];
        int high = r[i];
        int cur = dp[i];
        while (st.size() > 1) {
            pii second_last = st[st.size() - 2];
            // if the 2nd last point is within range
            // then the last point will no longer be interesting
            if (second_last.first >= low - 1) {
                // remove the last point
                st.pop_back();
            } else {
                // the 2nd last point is out of range
                break;
            }
        }
        dp[x] = min(st.back().second + 1, dp[x]);
        // continue to pop all the points that are no longer interesting.
        while (!st.empty() && st.back().second >= dp[x]) {
            st.pop_back();
        }
        // insert new interesting point
        st.emplace_back(x, dp[x]);
        dp[high] = min(dp[high], cur + 1);
    }
    return dp[n];
}

答案 2 :(得分:0)

你将创建一个二维数组,其中每个单元格都有一对(L,R),表示按特定位置下降的多米诺骨牌

初始位置表示由每个Domino推送(左,右):

   1      2     3       4      5      6      7     8
<0, 2> <1, 1> <2, 0> <0, 0> <0, 1> <1, 0> <0, 0> <2, 0>

有了这个,你不要通过做一个将数组减少到&lt; 0,0&gt;的移动来最小化数组。对。在这种情况下,将1移至R,3移至L或8移至L.

1到R New Array:

   1      2     3       4      5      6      7     8
<0, 0> <0, 0> <0, 0> <0, 0> <0, 1> <1, 0> <0, 0> <2, 0>

我们只有1个左移,8到L,因此新数组:

   1      2     3       4      5      6      7     8
<0, 0> <0, 0> <0, 0> <0, 0> <0, 0> <0, 0> <0, 0> <0, 0>

给我们一个2D数组:

   1      2     3       4      5      6      7     8
<0, 0> <0, 0> <0, 0> <0, 0> <0, 1> <1, 0> <0, 0> <2, 0>   // initial 
<0, 0> <0, 0> <0, 0> <0, 0> <0, 1> <1, 0> <0, 0> <2, 0>   // pushed 1 to R
<0, 0> <0, 0> <0, 0> <0, 0> <0, 0> <0, 0> <0, 0> <0, 0>   // pushed 8 to L

由于现在所有的细胞都是&lt; 0,0&gt;,我们确信所有的多米诺骨牌都倒下了,没有人留下来。