我偶然发现了上一次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,这就是我们的答案。
它有意义吗?对于我尝试过的所有测试都有效,我想相信它是正确的但我觉得某处可能存在泄漏。你能检查一下吗?这有什么好处吗?如果没有,为什么和这个思想过程有什么关系? :)
答案 0 :(得分:4)
这是一个不正确的解决方案。
您的解决方案的反例。假设,正方形中的一个是唯一重要的。您的解决方案将删除一条路。
答案 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)
我的算法基于以下观察:由于我们不关心仅具有不重要节点的循环,因此可以折叠不重要节点。我们正在折叠两个相邻的不重要节点,将它们替换为单个不重要节点,并使用原始节点的边缘总和。
当折叠两个不重要的节点时,我们需要处理两种特殊情况:
使用上面的节点折叠定义,算法是:
算法在O(M)时间内运行。我相信我可以证明它的正确性,但是在我花太多时间之前想得到你的反馈: - )