UOJ#554【UNR #4】挑战哈密顿【找哈密顿路径(调整法)】

题目描述

Link
提交答案题
给一个有向图,求最长的不经过重复点和边的路径,保证无重边自环。

题目分析

直接看官方题解咯

在做了点三染色使得没有同色边的问题后,我们学到了提交答案题的姿势:调整法,找一个不会使答案变劣,并且可以一直进行下去的调整方法。(如果进入死胡同就重来)

在这道题上,就是按随机顺序考虑加边,然后维护一个链的集合,每次考虑合并两条链,或者替换一条边。

写的过程中遇到一些问题:

  • 暴力判断是否会成环的版本:

    • 跑得很慢,基本卡卡地才跑完一次检验。发现是在走路径之前没有判断是否两个点都有入度/出度。判断之后再走就变得很快了。
    • 跑8,9个点的时候用了很长时间,第10个点卡在99996或者99997就动不了了。发现是随机数的问题,直接在windows下用 srand(rand()) 和 random_shuffle(),这样总共只有 2 15 2^{15} 215 种排序方式,跑得出来才怪了。正确的姿势是用 c++11 的 mt19937 及对应的 shuffle(…) 方式,这个的循环节(据说)是 2 19937 − 1 2^{19937}-1 2199371 的。
  • 改了之后仍然跑得比较慢,于是改成了 LCT 版本,但是发现速度差别不太大,在跑到 99996 之后仍然要跑很久 (>10min) 才能出结果。对照出题人的标程后发现是我每次都把所有的边全都拿来判断一遍,而优化后的标程是把那些与没有入度/出度的点相关的边拿来检验,这样在链长达到9999x的时候就只有常数条边需要检验了,快得出奇。

于是有一些总结:做提交答案题的时候如果跑得很慢或者效率很低下,那么肯定是有地方写错了/有一些明显的优化没有加,不能一味地就死等,想一想有没有地方还可以改进。一般标程都可以在很短的时间内出解。

另外,标程里面的文件打开方法也值得学习:
UOJ#554【UNR #4】挑战哈密顿【找哈密顿路径(调整法)】_第1张图片

最后放上我跑得并不快的Code:

#include
#define maxn 100005
#define maxm 300005
using namespace std;
int n,m,id[maxm],X[maxm],Y[maxm],L[maxn],R[maxn];
bool use[maxm]; int cnt;
map<long long,int>edge;
namespace LCT{
     
    int ch[maxn][2],fa[maxn],sum[maxn],v[maxn];
    bool rev[maxn];
    #define il inline
    #define pa fa[x]
    il bool isc(int x){
     return ch[pa][1]==x;}
    il bool isr(int x){
     return ch[pa][0]!=x&&ch[pa][1]!=x;}
    il void pd(int x){
     
        if(rev[x]){
     
            swap(ch[x][0],ch[x][1]),rev[x]=0;
            if(ch[x][0]) rev[ch[x][0]]^=1;
            if(ch[x][1]) rev[ch[x][1]]^=1;
        }
    }
    il void pdpath(int x){
     if(!isr(x)) pdpath(pa);pd(x);}
    il void rot(int x){
     
        int y=fa[x],z=fa[y],c=isc(x);
        if(!isr(y)) ch[z][ch[z][1]==y]=x;
        (ch[y][c]=ch[x][!c])&&(fa[ch[y][c]]=y);
        fa[ch[x][!c]=y]=x,fa[x]=z;
    }
    il void splay(int x){
     
        pdpath(x);
        for(;!isr(x);rot(x))
            if(!isr(pa)) rot(isc(pa)==isc(x)?pa:x);
    }
    il void access(int x){
     
        for(int y=0;x;x=fa[y=x]) splay(x),ch[x][1]=y;
    }
    il void bert(int x){
     
        access(x),splay(x),rev[x]^=1;
    }
    il int sert(int x){
     
        access(x),splay(x);
        for(;ch[x][0];x=ch[x][0]);
        return x;
    }
    il void link(int x,int y){
     
        bert(x);
        fa[x]=y;
    }
    il void cut(int x,int y){
     
        bert(x),access(y),splay(y);
        fa[x]=ch[y][0]=0;
    }
	il bool check(int x,int y){
     bert(x); return sert(y)==x;}
}
using namespace LCT;
mt19937 rnd;
int main()
{
     
	freopen("hamil10.in","r",stdin);
	freopen("hamil10.out","w",stdout);
	srand(time(0));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=10;i++) scanf("%*d");
	for(int i=1;i<=m;i++) scanf("%d%d",&X[i],&Y[i]),id[i]=i,edge[1ll*X[i]*(n+1)+Y[i]]=i;
	for(;cnt<n-1;){
     
		cerr<<cnt<<endl;
		//srand(rand());
		shuffle(id+1,id+1+m,rnd);
		for(int o=1,i;o<=m&&cnt<n-1;o++) if(!use[i=id[o]]){
     
			int x=X[i],y=Y[i];
			//cerr<
			if(R[x]&&L[y]) continue;
			if(check(x,y)) continue;
			if(!R[x]&&!L[y]) use[i]=1,cnt++,R[x]=y,L[y]=x,link(x,y);
			else if(rand()&1){
     
				if(R[x]) L[R[x]]=0,cut(x,R[x]),use[edge[1ll*x*(n+1)+R[x]]]=0,cnt--;
				if(L[y]) R[L[y]]=0,cut(L[y],y),use[edge[1ll*L[y]*(n+1)+y]]=0,cnt--;
				use[i]=1,cnt++,R[x]=y,L[y]=x,link(x,y);
			}
		}
	}
	printf("%d\n",n);
	for(int i=1;i<=n;i++) if(!L[i]){
     
		for(int x=i;x;x=R[x]) printf("%d ",x);
		return 0;
	}
}

你可能感兴趣的:(提交答案题,随机化)