图算法解决方案做得对吗?

时间:2012-02-07 17:27:45

标签: algorithm graph

我偶然发现了上一次Facebook黑客杯的问题(所以这不是我的作业,我觉得它非常有趣)而且我还想到了一个好奇但相当不错的解决方案。你能检查一下我的想法吗?这是任务:

  

您将获得一个拥有N个城市和M个双向道路连接的网络   这些城市。前K个城市很重要。您需要删除最小值   道路的数量使得在剩余的网络中没有周期   包含重要城市。循环是至少三种不同的序列   城市,使每对相邻的城市通过道路连接   序列中的第一个和最后一个城市也通过道路连接。

     

输入
  第一行包含测试用例T的数量。

     

每个案例都以包含整数N,M和K的行开头,它们代表   城市数量,道路数量和重要城市数量,   分别。这些城市的编号从0到N-1,以及重要的城市   编号从0到K-1。以下M行包含两个整数a [i]   和b [i],0≤i< M,代表通过道路连接的两个不同城市。

     

保证0≤a[i],b [i]&lt; N和a [i]≠b [i]。会有   两个城市之间的大多数公路。

     

输出
  对于从1到T的顺序编号的每个测试用例,输出“Case #i:”   后跟一个整数,即需要的最小道路数   删除,以便没有包含重要城市的周期。

     

约束
  1≤T≤20
  1≤N≤10000
  1≤M≤50000
  1≤K≤N

     

示例
  在第一个例子中,我们有N = 5个城市,它们由M = 7连接   道路和城市0和1很重要。我们可以拆除连接的两条道路   (0,1)和(1,2)和剩余的网络将不包含循环   重要城市。请注意,在剩余的网络中有一个循环   仅包含非重要城市,并且还有多种方式   移除两条道路并满足所有条件。一个人不能只删除一条道路   并摧毁包含重要城市的所有周期。

     

示例输入
  1
  5 7 2
  0 1
  1 2
  1 4
  0 2
  2 4
  2 3
  3 4

所以我这样想:在构建图表时,让我们有一个单独的数组,存储每个城市有多少邻居的信息(= =有多少条道路连接到给定城市)。在示例中,城市0有2,城市1有3,依此类推。让我们称这个数字为特定城市的“城市价值”。

在获得整个输入之后,我们遍历整个城市值数组,寻找值为1的城市。当达到一个时,意味着它不能处于循环中,所以我们减少它的值,“删除”(在不失一般性的情况下)将其连接到其唯一邻居并减少邻居价值的道路。在那之后,我们递归地去邻居检查相同的事情,如果值为1那么 - 重复该方案并递归地更深入。如果不是 - 请勿触摸。

在那次操作之后,我们已经清除了图形的所有部分,这些部分不是循环而不能是循环的一部分。我们也摆脱了所有没有任何意义的道路。所以我们称之为另一个功能,这一次 - 仅在重要城市工作。因此我们采用顶点1 - 在使用前一段中描述的函数之后,其值不能为1(因为它已经被函数调零)所以它是0或者是&gt; 1。在第一种情况下,我们不必做任何事情。在后者中,我们必须通过执行值-1删除来创建值1。与前一段类似,在每次移除后,我们减少这个城市及其邻居的价值,同时删除道路。我们重复所有k个重要城市,从所有重要城市总结价值-1,这就是我们的答案。

它有意义吗?对于我尝试过的所有测试都有效,我想相信它是正确的但我觉得某处可能存在泄漏。你能检查一下吗?这有什么好处吗?如果没有,为什么和这个思想过程有什么关系? :)

3 个答案:

答案 0 :(得分:4)

这是一个不正确的解决方案。

您的解决方案的

反例。假设,正方形中的一个是唯一重要的。您的解决方案将删除一条路。

Counterexample

答案 1 :(得分:3)

如果你能证明最佳切割次数等于包含重要节点的不同周期数*,那么解决这个问题就不那么难了。

您可以执行DFS,跟踪已访问的节点,每当您到达已访问过的节点时,您都​​会有一个循环。要判断循环是否包含重要节点,请跟踪每个节点的访问深度,并记住搜索当前分支中最后一个重要节点的深度。如果循环的起始深度比最后一个重要节点的深度小(即更早),则循环包含一个重要节点。

C ++实现:

// does not handle multiple test cases

#include <iostream>
#include <vector>

using namespace std;

const int MAX = 10000;

int n, m, k;
vector<int> edges[MAX];
bool seen[MAX];
int seenDepth[MAX]; // the depth at which the DFS visited the node

bool isImportant(int node) { return node < k; }

int findCycles(int node, int depth, int depthOfLastImp)
{
    if (seen[node])
    {
        if (seenDepth[node] <= depthOfLastImp && (depth - seenDepth[node]) > 2)
        {
            // found a cycle with at least one important node
            return 1;
        }
        else
        {
            // found a cycle, but it's not valid, so cut this branch
            return 0;
        }
    }
    else
    {
        // mark this node as visited
        seen[node] = true;
        seenDepth[node] = depth;

        // recursively find cycles
        if (isImportant(node)) depthOfLastImp = depth;
        int cycles = 0;
        for (int i = 0; i < edges[node].size(); i++)
        {
            cycles += findCycles(edges[node][i], depth + 1, depthOfLastImp);
        }
        return cycles;
    }
}

int main()
{
    // read data
    cin >> n >> m >> k;
    for (int i = 0; i < m; i++)
    {
        int start, stop;
        cin >> start >> stop;
        edges[start].push_back(stop);
        edges[stop].push_back(start);
    }

    int numCycles = 0;
    for (int i = 0; i < m; i++)
    {
        if (!seen[i])
        {
            // start at depth 0, and last important was never (-1)
            numCycles += findCycles(i, 0, -1);
        }
    }

    cout << numCycles << "\n";
    return 0;
}



*“不同”是指如果一个周期的所有边都已经是不同周期的一部分,则不计算周期。在以下示例中,我将循环数视为2而不是3:

    A–B
    | |
    C–D
    | |
    E–F

答案 2 :(得分:1)

我的算法基于以下观察:由于我们不关心仅具有不重要节点的循环,因此可以折叠不重要节点。我们正在折叠两个相邻的不重要节点,将它们替换为单个不重要节点,并使用原始节点的边缘总和。

当折叠两个不重要的节点时,我们需要处理两种特殊情况:

  1. 两个节点都连接到相同的不重要节点 U 。这意味着原始图中存在不重要的节点的循环;我们可以忽略循环,新节点将连接到具有单个边的相同不重要节点 U
  2. 两个节点都连接到相同的重要节点 I 。这意味着原始图中存在不重要节点和单个重要节点 I 的循环;在折叠节点之前,我们需要删除其中一条连接到重要节点 I 的边缘,从而删除循环;新节点将通过单边连接到重要节点 I
  3. 使用上面的节点折叠定义,算法是:

    1. 继续折叠相邻的不重要节点,直到没有相邻的不重要节点。如上面案例(2)中所定义的,重要不重要节点之间的所有删除边缘都会计入解决方案。
    2. 查找剩余图表的生成树,并删除生成树中未包含的所有边。在此步骤中删除的所有边都计入解决方案。
    3. 算法在O(M)时间内运行。我相信我可以证明它的正确性,但是在我花太多时间之前想得到你的反馈: - )