我试图解决这个问题 Problem Description 似乎正确的想法是检查给定的图形是否有循环(是否是树)。但是,我的代码无法通过测试7,(总是超出时间限制),任何想法如何使这更快?我用过DFS。非常感谢 是的,终于被接受了。问题是每个顶点上的dfs,这是不必要的。 dfs函数应该是这样的。
function dfs(idx: integer; id: integer): boolean;
begin
if (visited[idx] = id) then
begin
Result := false;
Exit;
end;
if (tree[idx] <> 0) then
begin
visited[idx] := id;
Result := dfs(tree[idx], id);
Exit;
end;
Result := true;
end;
program Project2;
{$APPTYPE CONSOLE}
var
i, m, j, n, k: integer;
tree: array [1 .. 25001] of integer;
visited: array [1 .. 25001] of boolean;
function dfs(idx: integer): boolean;
label
fin;
var
buf: array[1 .. 25001] of integer;
i, cnt: integer;
begin
cnt := 1;
while (true) do
begin
if (visited[idx]) then
begin
Result := false;
goto fin;
end;
if (tree[idx] <> 0) then
begin
visited[idx] := true;
buf[cnt] := idx;
Inc(cnt);
idx := tree[idx];
end
else
begin
break;
end;
end;
Result := true;
fin:
for i := 1 to cnt - 1 do
begin
visited[buf[i]] := false;
end;
end;
function chk(n: integer): boolean;
var
i: integer;
begin
for i := 1 to n do
begin
if (tree[i] = 0) then continue;
if (visited[i]) then continue;
if (dfs(i) = false) then
begin
Result := false;
Exit;
end;
end;
Result := true;
end;
begin
Readln(m);
for i := 1 to m do
begin
Readln(n);
k := 0;
for j := 1 to n do
begin
Read(tree[j]);
if (tree[j] = 0) then
begin
Inc(k);
end;
end;
if (k <> 1) then
begin
Writeln('NO');
end
else
if (chk(n)) then
begin
Writeln('YES');
end
else
begin
Writeln('NO');
end;
Readln;
end;
//Readln;
end.
答案 0 :(得分:2)
我对帕斯卡几乎一无所知,所以我可能误解了你在做什么,但我认为主要的罪魁祸首是fin
,你在那里取消标记被访问的顶点。这会强制您从每个顶点执行DFS,而您只需要为每个组件执行一个DFS。
如果有多个连接组件,则移动将停止
您不必担心回溯记录,因为此问题中每个顶点最多指向另一个顶点。也没有必要担心哪个DFS做了哪个标记,因为每个标记只能在其连接的组件中工作。
如果首先遇到指向自身的顶点,则不应该标记它,而是跳过它。
使用Set Union和Vertex / Edge Count的替代解决方案
由于树具有边数比顶点数少一个的特性,因此还有另一种思考问题的方法 - 确定(1)连通分量和(2)比较边和顶点计算每个组件。
在许多语言中,您都有一个Set数据结构,具有接近恒定时间的Union / Find方法。在这种情况下,解决方案简单快速 - 边缘数量接近线性。
为表示其连接组件的每个顶点创建一个Set。然后处理边缘列表。对于每个边,Union the Sets由两个顶点表示。随着时间的推移,跟踪每个Set中的顶点数和数字边。同样的例子:
初始设置
Vertex 1 2 3 4 5
Belongs to S1 S2 S3 S4 S5
Set S1 S2 S3 S4 S5
Has # vertices 1 1 1 1 1
And # edges 0 0 0 0 0
处理边缘从1到2
Vertex 1 2 3 4 5
Belongs to S1 S1 S3 S4 S5
Set S1 S3 S4 S5
Has # vertices 2 1 1 1
And # edges 1 0 0 0
处理边缘从2到3
Vertex 1 2 3 4 5
Belongs to S1 S1 S1 S4 S5
Set S1 S4 S5
Has # vertices 3 1 1
And # edges 2 0 0
处理边缘从3到4
Vertex 1 2 3 4 5
Belongs to S1 S1 S1 S1 S5
Set S1 S5
Has # vertices 4 1
And # edges 3 0
处理边缘从4到1
Vertex 1 2 3 4 5
Belongs to S1 S1 S1 S1 S5
Set S1 S5
Has # vertices 4 1
And # edges 4 0
我们可以在此停止,因为S1
此时违反了树的顶点与边数。 S1
中有一个周期。如果顶点5指向自身或其他人,则无关紧要。
对于后代,这是c中的实现。已经有一段时间了,所以原谅这种邋..它不是最快的,但它确实在时限内通过了所有测试。不相交的集合编码直接来自Wikipedia's pseudocode。
#include <stdio.h>
struct ds_node
{
struct ds_node *parent;
int rank;
};
struct ds_node v[25001];
void ds_makeSet(struct ds_node *x)
{
x->parent = x;
x->rank = 0;
}
struct ds_node* ds_find(struct ds_node *x)
{
if (x->parent != x) x->parent = ds_find(x->parent);
return x->parent;
}
int ds_union(struct ds_node *x, struct ds_node *y)
{
struct ds_node * xRoot;
struct ds_node * yRoot;
xRoot = ds_find(x);
yRoot = ds_find(y);
if (xRoot == yRoot) return 0;
if (xRoot->rank < yRoot->rank)
{
xRoot->parent = yRoot;
}
else if (xRoot->rank > yRoot->rank)
{
yRoot->parent = xRoot;
}
else
{
yRoot->parent = xRoot;
xRoot->rank++;
}
return 1;
}
int test(int n)
{
int i, e, z = 0;
for(i=1;i<=n;i++)
{
ds_makeSet(&v[i]);
}
for(i=1;i<=n;i++)
{
scanf("%d",&e);
if (e)
{
if ( !ds_union(&v[i],&v[e]) )
{
for(i++;i<=n;i++) scanf("%d",&e);
return 0;
}
}
else
{
z++;
}
}
return (z == 1);
}
int main()
{
int runs; int n;
scanf("%d", &runs);
while(runs--)
{
scanf("%d", &n);
getc(stdin);
test(n) ? puts("YES") : puts("NO");
}
}