竞赛图的哈密顿路径与哈密顿回路

更新日志 2025/07/17:开工。

前置知识

竞赛图

可以视作给无向完全图的每条边设置了一个方向产生的有向图。

哈密顿路径

一条只经过图上的边使得每个点都被经过且仅被经过 \(1\) 次的路径。

哈密顿回路

一条哈密顿路径,倘若末尾可以连向开头,那么就形成了一条哈密顿回路。

竞赛图有关性质

每个竞赛图必然存在哈密顿路径(Redei 定理),强连通竞赛图必然存在哈密顿回路(Camion-Moon 定理)。竞赛图缩点之后形成链状 DAG,且仍然是竞赛图。

以上性质都比较显然,可以感性理解或者自行搜索。

找哈密顿路径

以任意顺序依次向路径中加入当前图内的点即可,具体加入流程为:

  • 若当前点指向路径开头,那么就加入到路径开头。
  • 若当前点被路径结尾指向,那么就加入到路径结尾。
  • 否则顺次遍历路径找到第一个当前点可指向的点,然后把当前点插入到这个点前面。这个选中的位置前一个点必然指向当前点,且必然存在这样一个点,因为当前点必然指向路径结尾。

找哈密顿回路

首先这必须得是一个强连通竞赛图。

为了方便,我们先找出一条哈密顿路径,然后在路径上顺序遍历更新。

假如我们当前已经得到了左侧一个环和右侧一条链,其中链就是哈密顿路径的一部分,现在有这条链的下一个节点。

首先必然存在链尾连向当前点的边,因此可以直接把当前点加入链尾。

然后遍历整个环,如果能找到一个位置,使得环上这一点存在向链头的连边,且链尾可以连向下一点,那么就把整条链插入这个位置。

初始时找到第一个连向路径开头的位置即可得到一个环了。

为什么这样必然有解,考虑这个竞赛图强连通,且在上述策略下,如果一个点无法加入环,必然是因为环中全是连向这个点的(由于是按哈密顿路径来依次搞得因此环内至少有一个点连向当前点),那么当链尾存在向环的连边时,前一个位置必然连向链头。而整个图强连通,因此最后一个点必然指向环。

模板就直接详见代码了。

例题

[POI 2017] Turysta

上面说过,缩点之后整个图仍是 DAG,那么每个强连通分量内都可以跑哈密顿回路,然后在缩完点的图内 topo 即可,不难发现缩完点后,一个强连通分量指向另一个强连通分量,那么前者内每个节点都存在指向后者内每个节点的边,因此除了起始分量需要以起始点为回路起点,此后的所有分量均可任意开头遍历且任意位置结束,这样找方案会简单很多。

这道题重点就在于输出方案以练习找哈密顿回路。

code
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef __int128 i128;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;
template<typename Type> using vec=vector<Type>;
template<typename Type> using grheap=priority_queue<Type>;
template<typename Type> using lrheap=priority_queue<Type,vector<Type>,greater<Type>>;
#define fir first
#define sec second
#define pub push_back
#define pob pop_back
#define puf push_front
#define pof pop_front
#define chmax(a,b) a=max(a,b)
#define chmin(a,b) a=min(a,b)
#define rep(i,x,y) for(int i=(x);i<=(y);++i)
#define per(i,x,y) for(int i=(x);i>=(y);--i)
#define repl(i,x,y) for(int i=(x);i<(y);++i)
#define ReadFile(f) freopen(#f".in","r",stdin);freopen(#f".out","w",stdout);

struct mint {
	int x,mod;
	inline mint(int o=0,int p=998244353){x=o;mod=p;}
	inline mint & operator=(ll o){return x=o%mod,(x+=mod)>=mod&&(x-=mod),*this;}
	inline mint & operator+=(mint o){return (x+=o.x)>=mod&&(x-=mod),*this;}
	inline mint & operator-=(mint o){return (x-=o.x)<0&&(x+=mod),*this;}
	inline mint & operator*=(mint o){return x=(ll)x*o.x%mod,*this;}
	inline mint & operator^=(ll b){mint res(1,mod);for(;b;b>>=1,*this*=*this)if(b&1)res*=*this;return x=res.x,*this;}
	inline mint & operator^=(mint o){return *this^=o.x;}
	inline mint & operator/=(mint o){return *this*=(o^=(mod-2));}
	inline mint & operator++(){return *this+=1;}
	inline mint & operator--(){return *this-=1;}
	inline mint operator++(int o){mint res=*this;return *this+=1,res;}
	inline mint operator--(int o){mint res=*this;return *this-=1,res;}
	friend inline mint operator+(mint a,mint b){return a+=b;}
	friend inline mint operator-(mint a,mint b){return a-=b;}
	friend inline mint operator*(mint a,mint b){return a*=b;}
	friend inline mint operator/(mint a,mint b){return a/=b;}
	friend inline mint operator^(mint a,ll b){return a^=b;}
	friend inline mint operator^(mint a,mint b){return a^=b;}
	friend inline bool operator<(mint a,mint b){return a.x<b.x;}
	friend inline bool operator>(mint a,mint b){return a.x>b.x;}
	friend inline bool operator<=(mint a,mint b){return a.x<=b.x;}
	friend inline bool operator>=(mint a,mint b){return a.x>=b.x;}
	friend inline bool operator==(mint a,mint b){return a.x==b.x;}
	friend inline istream & operator>>(istream &in,mint &o){ll x;return in>>x,o=x,in;}
	friend inline ostream & operator<<(ostream &ot,mint o){return ot<<o.x,ot;}
};

typedef double flt;

template<typename Type> inline void read(Type &x){x=0;Type f=1,s=1;char ch=getchar();while(!isdigit(ch)&&~ch){if(ch=='-')f=-1;ch=getchar();}while(isdigit(ch))x=x*10+ch-48,ch=getchar();if(ch=='.'){ch=getchar();while(isdigit(ch))s/=10,x=x+(ch-48)*s,ch=getchar();}x*=f;}
template<typename Type> inline void write(Type x){if(x<0)putchar('-'),x=-x;if(x>9)write(x/10);putchar(x%10+'0');}
inline void read(string &s){s.clear();char ch=getchar();while(isspace(ch))ch=getchar();while(!isspace(ch)&&~ch)s+=ch,ch=getchar();}
inline void read(char &ch){ch=getchar();while(isspace(ch))ch=getchar();}
inline void write(const string &s){for(auto c:s)putchar(c);}
inline void write(const char &ch){putchar(ch);}
inline void write(const flt &x){printf("%.18LF",(long double)x);}
inline void write(const mint &m){write(m.x);}
inline void write(const char *x){while(*x)putchar(*(x++));}
template<typename Type,typename...T> inline void read(Type &x,T&...y){read(x),read(y...);}
template<typename Type> inline void put(const Type &x,char ch='\n'){write(x);if(ch)putchar(ch);}

const int inf=0x3f3f3f3f;
const ll INF=0x3f3f3f3f3f3f3f3f;

const int N=2e3+5;

int n;
vec<int> g[N];

int dcnt;
int dfn[N],low[N];
stack<int> stk;bool ins[N];
int scnt;
int scc[N],siz[N];
vec<int> SCC[N];
void tarjan(int now){
    dfn[now]=low[now]=++dcnt;
    stk.push(now),ins[now]=1;
    for(auto nxt:g[now]){
        if(!dfn[nxt]){
            tarjan(nxt);
            chmin(low[now],low[nxt]);
        }else if(ins[nxt])chmin(low[now],dfn[nxt]);
    }
    if(low[now]>=dfn[now]){
        ++scnt;
        while(1){
            int tp=stk.top();
            scc[tp]=scnt,++siz[scnt];
            stk.pop();ins[tp]=0;
            SCC[scnt].pub(tp);
            if(tp==now)break;
        }
    }
}

vec<int> g2[N];
int d[N];

int tcnt;
int nb[N],arr[N];
void topo(){
    queue<int> q;
    rep(i,1,scnt)if(!d[i])q.push(i);
    while(!q.empty()){
        int now=q.front();q.pop();
        arr[nb[now]=++tcnt]=now;
        for(auto nxt:g2[now])if(!--d[nxt])q.push(nxt);
    }
}

int suf[N];

int hd[N],tl,pne[N];
bool G[N][N];
void gpath(int sid){
    hd[sid]=0,tl=0;
    for(auto i:SCC[sid]){
        if(!hd[sid]){
            hd[sid]=tl=i;
            continue;
        }
        if(G[i][hd[sid]])pne[i]=hd[sid],hd[sid]=i;
        else if(G[tl][i])pne[tl]=i,tl=i;
        else{
            for(int j=hd[sid];1;j=pne[j])if(G[i][pne[j]]){
                pne[i]=pne[j];
                pne[j]=i;
                break;
            }
        }
    }
    int k=0;
    for(int i=hd[sid];i;i=pne[i])if(G[i][hd[sid]]){
        k=i;
        break;
    }
    for(int i=pne[k];i;i=pne[i]){
        if(G[i][hd[sid]]&&G[k][pne[k]])k=i;
        else{
            for(int j=hd[sid];j!=k;j=pne[j]){
                if(G[i][pne[j]]&&G[j][pne[k]]){
                    int be=pne[k];
                    pne[k]=pne[i];
                    pne[i]=pne[j];
                    pne[j]=be;
                    i=k;
                    break;
                }
            }
        }
    }
    if(!k)k=hd[sid];
    pne[k]=hd[sid];
    if(siz[sid]>1)for(int i=pne[k];1;i=pne[i]){
        assert(G[i][pne[i]]);
        if(i==k)break;
    }
}

inline void Main(){
    read(n);
    rep(i,1,n)repl(j,1,i){
        int o;read(o);
        if(o)g[j].pub(i);
        else g[i].pub(j);
    }
    rep(i,1,n)if(!dfn[i])tarjan(i);
    rep(i,1,n)for(auto j:g[i]){
        if(scc[i]!=scc[j])g2[scc[i]].pub(scc[j]),++d[scc[j]];
        else G[i][j]=1;
    }
    topo();
    per(i,scnt,1)suf[i]=suf[i+1]+siz[arr[i]];
    rep(i,1,scnt)gpath(i);
    rep(i,1,n){
        put(suf[nb[scc[i]]],' ');
        put(i,' ');
        for(int j=pne[i];j!=i;j=pne[j])put(j,' ');
        rep(j,nb[scc[i]]+1,scnt){
            put(hd[arr[j]],' ');
            for(int k=pne[hd[arr[j]]];k!=hd[arr[j]];k=pne[k])put(k,' ');
        }
        puts("");
    }
}
int main(){
	// ReadFile(___);
	// #define MultiTasks
	#ifdef MultiTasks
	int t;read(t);while(t--)
	#endif
	Main();
	return 0;
}
posted @ 2025-07-17 19:47  LastKismet  阅读(111)  评论(0)    收藏  举报