考虑第一个点有n条自环的情况,并且假设n条自环边的编号由内向外递增0,1,2…
第一次错误的修改和最终正确的修改的区别在于for循环中i是如何改变的
1.错误的i = ne[i]
2.正确的i = h[u]
为什么1是错的呢?
因为即使在循环内已经把h[u]
改为了ne[i]
,但在第一层dfs中,i
能被h[u]
影响到的只有在第一次赋值时,所以即使把h[u] = ne[i]
,但在回溯到第一层时,i = ne[i]
,而ne[i]
即为第一条边的下一条边(也就是第二条边,如此往复,第二条边的下一边是第三条边…即还是要循环n次)。同理,在第二层时,因为在第一层已经把h[u]
改为了第一层的ne[i]
即第二条边,所以第二层从第二条自环开始循环,要循环n-1
次,总的时间复杂度还是平方级别
为什么2是对的呢?
因为在i
改变由h[u]
决定,在第一层时h[u]
已经变为第二条边,所以到第二层时从第二条边开始循环,在第二层时h[u]
又改为了第三条边,所以第三层从第三条边开始....以上和1是一样的,区别在于回溯的时候,比如回溯到了第一层,i
是通过h[u]
来改变的,而此时h[u]
已经变成了-1
所以跳出循环,第一层只执行了一次,所以是线性的。
第一次错误的修改
void dfs(int u)
{
for(int i = h[u] ; ~i ; i = ne[i])
{
if(used[i])
{
h[u] = ne[i];
continue;
}
used[i] = true;
if(type == 1)used[i ^ 1] = true;
int t;
if(type == 1)
{
t = i / 2 + 1;
if(i & 1)t = -t;
}
else t = i + 1;
int j = e[i];
h[u] = ne[i];
dfs(j);
res[++ cnt] = t;
}
}
最终正确,不使用引用的dfs代码(原理一样)
void dfs(int u)
{
for(int i = h[u] ; ~i ; i = h[u])//区别
{
if(used[i])
{
h[u] = ne[i];//区别
continue;
}
used[i] = true;
if(type == 1)used[i ^ 1] = true;
int t;
if(type == 1)
{
t = i / 2 + 1;
if(i & 1)t = -t;
}
else t = i + 1;
int j = e[i];
h[u] = ne[i];//区别
dfs(j);
res[++ cnt] = t;
}
}
y总dfs代码
void dfs(int u)
{
for (int &i = h[u]; ~i;)//区别
{
if (used[i])
{
i = ne[i];//区别
continue;
}
used[i] = true;
if (type == 1) used[i ^ 1] = true;
int t;
if (type == 1)
{
t = i / 2 + 1;
if (i & 1) t = -t;
}
else t = i + 1;
int j = e[i];
i = ne[i];//区别
dfs(j);
ans[ ++ cnt] = t;
}
}
假设节点1有5个自环,对应编号为1-5
错误优化模拟:
在dfs遍历到第一层,令h[u]=2,遍历到第2层时,令h[u]=3,直到最后一层h[u]=-1
回溯到第4层,i=ne[i]=5,判断后continue;回溯
回溯到第3层,i=ne[i]=4,判断后continue;i=ne[i]=5,判断后continue;回溯
回溯到第2层,i=ne[i]=3,判断后continue;i=ne[i]=4,判断后continue;i=ne[i]=5,判断后continue;回溯
回溯到第1层,i=ne[i]=2,判断后continue;i=ne[i]=3,判断后continue;i=ne[i]=4,判断后continue;i=ne[i]=5,判断后continue;回溯
正确优化模拟:
在dfs遍历到第一层,令h[u]=2,遍历到第2层时,令h[u]=3,直到最后一层h[u]=-1
回溯到第4层,i=h[u]=-1;回溯
回溯到第3层,i=h[u]=-1;回溯
回溯到第2层,i=h[u]=-1;回溯
回溯到第1层,i=h[u]=-1;回溯
大哥厉害,我 看的我的醍醐灌顶
手动模拟就清晰多了
请问为什么把y总的代码里两个 i = ne[i] 放在for循环里就不正确(保留引用)?
得在下一次递归前修改i
请问是为什么呢?我的理解只可能会T,不会Wa
y总视频好像有解释,有强调要先把边删了再递归儿子,不然儿子也会重复遍历这条边,所以放在 $for$ 循环是不行的,那样的话是递归回来才把边删了,所以不删的话就相当于没有优化,当然不影响正确性。