题目描述
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 219937−1 的。
-
改了之后仍然跑得比较慢,于是改成了 LCT 版本,但是发现速度差别不太大,在跑到 99996 之后仍然要跑很久 (>10min) 才能出结果。对照出题人的标程后发现是我每次都把所有的边全都拿来判断一遍,而优化后的标程是把那些与没有入度/出度的点相关的边拿来检验,这样在链长达到9999x的时候就只有常数条边需要检验了,快得出奇。
于是有一些总结:做提交答案题的时候如果跑得很慢或者效率很低下,那么肯定是有地方写错了/有一些明显的优化没有加,不能一味地就死等,想一想有没有地方还可以改进。一般标程都可以在很短的时间内出解。
另外,标程里面的文件打开方法也值得学习:
最后放上我跑得并不快的Code:
#include<bits/stdc++.h>
#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<<x<<' '<<y<<endl;
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;
}
}