Ata*_*hev 6 algorithm dynamic-programming
我一直在PEG Online Judge中遇到一个问题,称为Dominos,你可以在这里找到:
http://wcipeg.com/problem/domino
简要描述:
我们给出了一个水平排列的不同高度和位置的多米诺骨牌列表.位置x的高度为h的多米诺骨牌,一旦被推向右侧,将在x + 1,x + 2,...,x + h位置向右敲击所有多米诺骨牌.相反,推向左侧的同一多米诺骨牌会将所有多米诺骨牌敲到位置x-1,x-2,...,xh向左.
推翻所有多米诺骨牌的最小推动次数是多少?
例:
|
| |
| | | | | |
1 2 3 4 5 6 7 8
Run Code Online (Sandbox Code Playgroud)
答案是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有两个选择:向左移动(在这种情况下它会推翻其他多米诺骨牌)或向右移动(在这种情况下,左边的另一个多米诺骨牌将其翻倒).尝试从那里工作.
谢谢!
这是一个O(N log N)解决方案:
让我们弄清楚如何计算如果我们将多米诺骨牌推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)
Run Code Online (Sandbox Code Playgroud)
该代码以线性时间运行(每个多米诺骨牌只被推送一次,最多弹出一次)。它可能看起来不是很直观(我不会在这里写出完整的正式证明,因为它太长了),但是手动完成小示例可以帮助理解为什么它是正确的。
事实上,这种技术值得理解,因为它通常用于竞争性编程(当某物从右向左移动时,我们需要找到满足每个右侧元素的某些条件的最左边的元素。我知道这听起来有点模糊) 。
我们可以用同样的方式计算线性时间内的(如果我们将多米诺骨牌推到右边R[i]我们可以走多远)。i-th
现在我们知道如果我们选择将多米诺骨牌推向任何方向会发生什么。凉爽的!
让我们使用动态规划来计算答案。让f(i)为我们需要采取的最小行动数量,以便所有多米诺骨牌都i-th被推倒,而其余的仍然保持不变。转换非常自然:我们要么将多米诺骨牌推向左侧,要么向右推。在前一种情况下,我们进行转换f(j) + 1 -> f(i),其中L[i] - 1 <= j < i. 在后一种情况下,过渡是f(i - 1) + 1 -> f(R[i])。该解决方案是正确的,因为它为每个多米诺骨牌尝试了所有可能的操作。
如何让这部分变得高效?我们需要支持两种操作:更新一个点中的值和获取范围内的最小值。线段树可以在 中处理它们O(log N)。它给了我们一个O(N log N)解决方案。
如果这个解决方案看起来太难,您可以首先尝试实现一个更简单的解决方案:只需运行模拟来计算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;
}
Run Code Online (Sandbox Code Playgroud)