原题链接:Icy Itinerary - Problem - QOJ.ac
题意
给定一个图,有n个点,m条边,要求构造一个以1开头点的排列,使得该排列中只存在一个点ai满足:ai与ai-1和ai+1的其中一点有连边,且与另外一点无连边。
数据规模:n,m≤3e5
题目理解
将原题意翻译一下,就是说要求我们将原图划分为两个互补的集合,使得其中一个集合中存在一条哈密顿路,另一个集合的补图中存在一条哈密顿路。
证明:假设ai-1与ai之间有连边,ai+1与ai之间没有连边,则易推出ai-1与ai-2,ai-2与ai-3…之间一定有连边,而ai+1与ai+2,ai+2与ai+3…之间无连边,则a1…i这个集合中相邻两点有连边,ai+1…n这个集合中相邻两点无连边。即可知题意实际为将原图划分为两个互补的集合,使得其中一个集合中存在一条哈密顿路,另一个集合的补图中存在一条哈密顿路。
对于这个构造,我们可以这样考虑:假设我们已知s1为原图中存在哈密顿路的集合,t1为该哈密顿路的终点,s2为补图中存在哈密顿路的集合,t2为该补图中哈密顿路的终点,ai为我们当前考虑加入的点。
此时有三种情况:
1.ai与t1之间有连边,则可以将ai加入s1,并将ai作为新的t1;
2.ai与t2之间无连边,则可以将ai加入s2,并将ai作为新的t2;
3.ai与t1之间无连边,且ai与t2之间有连边,此时又有两种情况:
若t1与t2之间有连边,不难发现t1-t2-ai为一条哈密顿路,则我们可以将t2从s2中取出,并加入s1,然后再将ai加入s1;
若t1与t2之间无连边,不难发现t2-t1-ai为补图中的一条哈密顿路,则我们可以将t1从s1中取出,并加入s2,然后再将ai加入s2。
细节思考
1.当s1和s2中有一个空集时,我们应当优先考虑将新的节点放入空集,避免将虚假节点放入集合;
2.由于题目要求构造以1开头的排列,所以我们可以考虑倒序加入节点,并记录1节点所在集合,再倒序输出,保证1节点一定在开头。
代码实现
#include<bits/stdc++.h>
using namespace std;
int const N=1e6+10;
map<int,int>G[N];
int pa1[N],pa2[N],n,m,cnt1,cnt2;
int main(){
cin>>n>>m;
if(m==0){
for(int i=1;i<=n;i++)cout<<i<<" ";
return 0;
}
for(int i=1;i<=n;i++)G[0][i]=G[i][0]=1;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
G[x][y]=1;
G[y][x]=1;
}
for(int i=n;i>=1;i--){
if(G[i][pa1[cnt1]])pa1[++cnt1]=i;
else{
if(!G[i][pa2[cnt2]])pa2[++cnt2]=i;
else{
if(G[pa1[cnt1]][pa2[cnt2]]){
if(cnt2){
pa1[++cnt1]=pa2[cnt2];
pa1[++cnt1]=i;
cnt2--;
}
else{
pa2[++cnt2]=i;
}
}
else{
pa2[++cnt2]=pa1[cnt1];
pa2[++cnt2]=i;
cnt1--;
}
}
}
}
if(pa1[cnt1]==1){
for(int i=cnt1;i>=1;i--)cout<<pa1[i]<<" ";
for(int i=cnt2;i>=1;i--)cout<<pa2[i]<<" ";
}
else{
for(int i=cnt2;i>=1;i--)cout<<pa2[i]<<" ";
for(int i=cnt1;i>=1;i--)cout<<pa1[i]<<" ";
}
return 0;
}