Problem
有一个N(≤10000)颗石子围成的环,石子从0到N-1编号。从第i颗石子可以到第(i*2)%N颗石子和第(i*2+1)%N颗石子。求一条从0开始、最后回到0的、字典序最大的哈密顿回路。无解输出-1。
Solution
首先考虑无解的情况。(通过打表发现)我们猜测N为奇数时会无解。其实证明也很简单:
能到达点0的只有
⌊N2⌋
⌊
N
2
⌋
,而能到达点N-1的则需要分类讨论:当N为奇数时,N-1为偶数,所以
⌊N2⌋=N−12
⌊
N
2
⌋
=
N
−
1
2
;当N为偶数时,N-1为奇数,所以
⌊N2⌋=⌊N−12⌋+1
⌊
N
2
⌋
=
⌊
N
−
1
2
⌋
+
1
。综上,当N为奇数时,能到达点0和点N-1的均只有
N−12
N
−
1
2
,但除0以外的每个点只能经过1次,所以不可能从点
N−12
N
−
1
2
到一遍点N-1再到一遍点0。所以当N为奇数时无解。
那么考虑N为偶数的情况。对于点X(≤
N2
N
2
)而言,它的出边有两条:一条连向点(X*2)%N,一条连向(X*2+1)%N;而对于点X+
N2
N
2
而言,它的出边也有两条:一条连向点(X*2+N)%N=(X*2)%N,一条连向(X*2+1+N)%N=(X*2+1)%N。所以点X(≤
N2
N
2
)和点X+
N2
N
2
的出边是一样的。那么由于要求一条哈密顿回路,如果我们从点X走到点(X*2)%N,则我们必须从点X+
N2
N
2
走到点(X*2+1)%N,反之亦然。
我们发现本图中有N个点,每个点有两条出边,所以本图有2N条边。由于要求哈密顿回路,所以我们必须在每个点的出边中只选择一条去走,也就是说只选择N条边。
根据上面的分析,我们将点X和点X+
N2
N
2
合并为1个点,而此点则仍有两条出边:一条连向点(X*2)%N,一条连向(X*2+1)%N。这样,我们就把原图缩成了一个有
N2
N
2
个点、N条边的新图。而原来经过N条边的要求依然不变。所以题目被转化为求一条欧拉回路。
那么我们只消码一个简洁的dfs即可优美地解决问题:一开始从点0出发,然后由于要求字典序最大,尽量地往编号大的点走,并将走过的边删掉(或标记一下);当我们在某个点上因其出边都被删掉无路可走时,我们就把这个点压进栈中(或数组里);最后从栈顶不断输出(逆序输出)。
但是理解它可不像打它这么简单。我们可以举个栗子:当N为6时,我们构出来的新图如下图所示:
由于点3、4、5与点0、1、2的出边相同,所以我们不必构出它们的出边;而当我们遍历到它们时,我们就把它们%一个
N2
N
2
,但是在栈中的答案依然是它们原本的数。
首先我们从0出发,到点1,再到点3,然后3%一个
N2
N
2
变成点0,但是由于不能再走边0
→
→
1,所以只能走边0
→
→
0,然后0的两条出边都被走过了,就把0压入栈中,弹出。
回到点3(我们是从点3到点0的),和点0的情况一样,所以把3压入栈中,弹出。
回到点1(我们刚才从1走到点3),到点2,再到点5,5%一个
N2
N
2
变成点2,再到点4,4%一个
N2
N
2
变成点1,而点1的两条出边也都被走过了,于是把点4压入栈中(因为我们此时在点4而非点1),弹出。
回到点5(因为我们是从点5而非点2走到点4的),发现它的两条出边(去4的边和去5的边)都被走过,于是把点5压入栈中,弹出。
回到点2,一样压入栈中弹出。最后点1、点0也一样。
那么我们得到栈ans={0,3,4,5,2,1,0}。逆序输出则为:“0 1 2 5 4 3 0”。
我们发现在此步骤中,我们在3的时候就已经遍历回点0了。但我们要求的是终点为0的一条欧拉回路,所以我们可以视为当时并没有去从点1走到点3,而是从点1走到了点2;而由于这种情况下点1肯定会遍历完它所有的出边,所以出边和它一样的点4肯定会无路可走,而且其他点均暂时不会碰到无路可走的情况,所以我们肯定是在点4时打破这个僵局,然后把4压入了栈,所以可以视为我们是从点4走到了点3。
时间复杂度:
O(n)
O
(
n
)
。
Code
#include <cstdio>
#include <cstdlib>
#include <vector>
using namespace std;
#define N 10010
#define fo(i,a,b) for(i=a;i<=b;i++)
int i,n;
vector<int>side[N],ans;
void dfs(int x)
{
int now=x;
x%=n/2;
while(!side[x].empty())
{
int y=side[x].back();
side[x].pop_back();
dfs(y);
}
ans.push_back(now%n);
}
int main()
{
scanf("%d",&n);
if(n&1)
{
printf("-1");
exit(0);
}
fo(i,0,(n-1)/2)
{
side[i].push_back(2*i);
side[i].push_back(2*i+1);
}
dfs(0);
while(!ans.empty())
{
printf("%d ",ans.back());
ans.pop_back();
}
}