O(nlogn)中的DAG最小路径覆盖?

时间:2013-10-28 15:54:22

标签: algorithm graph intervals

最近October 20-20 Hack on Hackerrank提出了以下问题:

  

邪恶的国家A很生气,并计划在该地区发射N枚导弹   和平民族B试图消灭国家B的所有人。   国家A的导弹我将在时间ti抵达B国。导弹我   通过独特的无线电信号与其总部进行通信   频率等于fi。你能帮助和平的国家B生存吗?   建立一个防御系统,阻止导弹死亡   天空?

     

防御系统:

     

捍卫国家B免受攻击导弹的唯一方法是   用黑客导弹攻击他们。你有很多   hackerX导弹和它们中的每一个都有自己的无线电频率。一个   个别黑客X导弹可以摧毁邪恶国家A的攻击   导弹如果两枚导弹的射频匹配。每   hackerX导弹可以无限次使用。它的   无敌,不会在碰撞中被摧毁。

     

好消息是你可以调整hackerX导弹的频率   以配合邪恶导弹的频率。更改hackerX时   导弹的初始频率fA到新的防御频率fB,你   需要| fB - fA |时间单位。

     如果两个邪恶的导弹同样如此   频率同时到达,我们可以用它们摧毁它们   黑客X导弹。你可以将hackerX导弹的频率设置为任意   它被解雇时的价值。

     

您必须启动的最低数量的hackerX导弹才能保留   国家B安全吗?

     

输入格式:第一行包含一个表示的整数N.   导弹数量。接下来是N行,每行包含两行   整数ti和fi表示时间和第i导弹的频率。

     

输出格式:表示最小数量的单个整数   hackerX你需要保卫国家。

     

约束:   1 <= N <= 100000

     

0&lt; = ti&lt; = 100000

     

0&lt; = fi&lt; = 100000

     

t1&lt; = t2&lt; = ...&lt; = tN

问题变为最小路径覆盖问题,该问题将基于约束在O(nlogn)时间内解决。 然而,最小路径覆盖问题的最佳解决方案是使用Hopcroft-Karp算法,这导致最大匹配问题。解决方案是O(n ^ 2.5)。 但是icyrhyme9733的以下解决方案解决了O(nlogn)中的问题:

#include <cmath>
#include <cstdio>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;


int main() {
    /* Enter your code here. Read input from STDIN. Print output to STDOUT */
    int n;
    cin >> n;
    vector<pair<int, int> > vp;
    for(int i = 0; i < n; ++i) {
        int t, f;
        cin >> t >> f;
        vp.push_back(make_pair(t + f, t - f));
    }
    sort(vp.begin(), vp.end());
    reverse(vp.begin(), vp.end());
    vector<int> last(vp.size(), numeric_limits<int>::max());
    for(int i = 0; i < vp.size(); ++i) {
        *lower_bound(last.begin(), last.end(), vp[i].second) = vp[i].second;
    }
    cout << lower_bound(last.begin(), last.end(), numeric_limits<int>::max()) - last.begin() << endl;
    return 0;
}

是否在O(nlogn)时间内解决了最小路径覆盖问题?或者是否有我缺少的优化?

我在this thread上发现了类似的问题。他们利用了图表是区间图的事实。即使非常相似,也不能实现相同的解决方案,因为间隔是反转的。

3 个答案:

答案 0 :(得分:3)

以这种方式思考:

步骤1:描述1个黑客导弹可以覆盖2枚导弹

的情况

给出2枚导弹i和j使得t j &gt; = t i (导弹按到达时间排序):

如果它们之间的频率差异小于或等于它们到达之间的时间量,我可以使用相同的黑客导弹来阻止i和j(因为它需要1个单位的时间来改变频率1)。

更正式的是,同样的黑客导弹可以覆盖 i和j,如果:

  

t j - t i &gt; = | f i - f j |

让我们简化:

第2步:做出假设

我们现在假设

  

f i &gt; ˚F<子>Ĵ

所以我们可以摆脱讨厌的绝对值符号,产生

  

t j - t i &gt; = f i - f j

我们稍后会删除这个假设。

将f j 添加到双方

  

t j - t i + f j &gt; = f i

将t i 添加到双方

  

t j + f j &gt; = t i + f i

我们知道至少需要1枚黑客导弹才能覆盖第一枚到达的导弹。问题是,它可以覆盖多少其他到达的导弹?

嗯,通过列表进行线性扫描会告诉我们。我们可以检查每个后续值是否满足不等式。但这会给我们O(n 2 )时间,我们可以做得更好。

步骤3:根据假设

找到有效的解决方案

首先,让我们按到达时间对值进行排序,并将关系分解为t i + t j

这类似于代码中的一行:

sort(vp.begin(), vp.end());

(代码的作者之后将其反转为可以使用std::lower_bound,我们需要它,因为它会将迭代器返回到值&gt; =查询值,而std::upper_bound是严格的&lt; ;查询价值)

由于我们知道至少需要1枚黑客导弹才能覆盖第一枚到达的导弹,让我们将黑客导弹添加到我们将要部署的黑客导弹列表中,让我们用最新的标记来标记它们。 sub> i + f i 它们涵盖。

那么,我们该怎么办?当我们遍历国家A的导弹列表时,我们需要尝试在国家B的部署黑客导弹列表中找到一个可以覆盖的导弹。

这通过我们的部署导弹列表减少到二分搜索,这样它也可以覆盖新导弹。回想一下,如果

,导弹可以覆盖i和j
  

t j + f j &gt; = t i + f i

列表中的黑客导弹与t i + f i 相关联,我们需要覆盖的新导弹与t j + f j

如果我们能找到这样的黑客导弹,用t j + f 替换其关联的t i + f i Ĵ

执行此操作的代码行是

*lower_bound(last.begin(), last.end(), vp[i].second) = vp[i].second;

来自documentation

  

lower_bound(first,last,val)返回指向的迭代器   范围[first,last]中的第一个元素,它不会比较少   比val。   换句话说,它搜索大于或等于 val

的值

我们不必反转列表并使用lower_bound,但upper_bound严格检查&lt;,而不是&lt; =。所以我们需要解决逆转并使用lower_bound 请注意,lower_bound的运行时间是对数的,因为它本质上是一个二进制搜索。如果失败,我们会插入一个新的黑客导弹来覆盖我们的新导弹。

代码的作者通过创建一个大小为n的黑客导弹列表并将它们与无穷大的值相关联来绕过严格的插入:

vector<int> last(vp.size(), numeric_limits<int>::max());

当我们用最先进的黑客导弹打击覆盖最新的导弹时,我们最终得到了一套最小的导弹。现在我们只需要获得我们添加到列表中的黑客导弹数量。如果我们严格插入,我们可以调用vector.size(),但由于作者没有,他执行迭代器减法以获得与无穷大相关联的起始和第一个黑客导弹之间的元素数量(或{{1 }}):

vector.end()

这篇文章正在进行中,我会在弄清楚的时候更新它。

答案 1 :(得分:2)

我会尝试用比特解释代码(无论我能理解什么)。 IMO它不是作为最大匹配算法解决的,因为它是基于区间的,并且它减少了大部分搜索空间。

代码使用以下想法。假设在时间t1和t2有以下两枚导弹。

  (t1-f1)------t1------(t1+f1)  
(t2-f2)---t2---(t2+f2)

现在也将hackerX导弹t2用于t1,
t1-t2> f1-f2(为简单起见摆脱mod)
要么     t1-f1&gt; T2-F2

for(int i = 0; i < n; ++i) {
  int t, f;
  cin >> t >> f;

  // pair's first part is max time till this hacker missile will stay valid.
  // pair's second part is t-f for comparison between consecutive missiles.
  vp.push_back(make_pair(t + f, t - f));
}

// sort on the basis of first part of pair.
sort(vp.begin(), vp.end());

reverse(vp.begin(), vp.end());

// vector containing initially very large numbers.
vector<int> last(vp.size(), numeric_limits<int>::max());

// start from the last time.
for(int i = 0; i < vp.size(); ++i) {

   // find the 1st entry that is not smaller than vp[i].second.
   // Make it equal to vp[i].second    
  *lower_bound(last.begin(), last.end(), vp[i].second) = vp[i].second;
}
// The number of new entries = number of hackerX missiles needed.
cout << lower_bound(last.begin(), last.end(), numeric_limits<int>::max()) - last.begin() << endl;

答案 2 :(得分:0)

使用Dilworth's Theoren

将上述问题简化为O(nlogn)

可以找到关于上述定理的好教程here