旅行(不是加强版)
加强版
加强版数据范围:
我们注意到
也就是说要么是个树,要么是个基环树
60pts
这60分是个树,可以简单的贪心想到每次都走子树中编号最小的那个,并且把1作为根
dfs练手题
还是贴个代码叭
void dfs1(int now,int fa) { if(vis[now])return ; ans1[++t]=now; vis[now]=1; vector<int> qwq;//为了不使存储的点被后面的子树覆盖,所以用vector for(int e=head[now];e;e=ed[e].nxt) { int v=ed[e].to; if(v==fa)continue; qwq.push_back(v); } sort(qwq.begin(),qwq.end());//vector的排序 int qaq=qwq.size(); for(int i=0;i<qaq;i++) dfs1(qwq[i],now); }
100pts
大多数的解法
当m==n时,它是一个基环树(即树上挂着一个环的树)
基环树只要删掉环上的一条边,它就是个树了。所以我们可以枚举删掉哪条边。(需要开个\(O_2\))
由于开\(O_2\)会让你的评测记录显得不优雅,我们要考虑考虑怎么不开\(O_2\)过掉这道题。
由于我们要删去环上的边,所以要先找个环,而不是暴力删边再判是否是环上的。这样就可以过去了。
但是博主脑洞清奇所以并没有用这种做法当然也没有代码
当然不是应某神仙的要求贴上他的代码
oid dfs3(int from,int fa) {//找环代码 vis[from]=1; for(int i=0;i<a[from].size();i++) {//这里是用vector记录的出边 int to=a[from][i]; if(to==fa) continue ; if(vis[to]) { flag=1;//找到了环 cir1[to]=1;//标记to和from都在环上 cir1[from]=1; u1[++cnt]=from;//记录在环上的点 v1[cnt]=to; return ; } dfs3(to,from); if(flag&&cir1[to]) if(cir1[from]) {//判断找到了环的“根”(即以1为根时,最靠近1的在环上的点) flag=0; u1[++cnt]=from; v1[cnt]=to; return ; } else { cir1[from]=1; u1[++cnt]=from; v1[cnt]=to; return ; } } }
全套代码
#include<cstdio> #include<cstring> #include<vector> #include<algorithm> using namespace std; vector<int> a[5010]; int n,m; int res[5010],ans[5010],tot; int cir1[5010]; int u1[10010],v1[10010],cnt; int vis[5010]; int du,dv; int flag; struct Edge {//咱也不造为啥铁锤妹妹要写前向星(虽然后面也没有用到前向星) int from,to; }e[5010]; void dfs2(int u,int fa) {//没有环时的dfs if(vis[u]) return ; vis[u]=1; ans[++tot]=u;//记录答案 for(int i=0;i<a[u].size();i++) { int v=a[u][i]; if(v==fa) continue ; dfs2(v,u); } } void dfs1(int u,int fa) {//有环时的dfs if(vis[u]) return ; vis[u]=1; res[++tot]=u; for(int i=0;i<a[u].size();i++) { int v=a[u][i]; if(v==fa) continue ; if((u==du&&v==dv)||(u==dv&&v==du))//du,dv为枚举删去的边(见主函数) continue ; dfs1(v,u); } } void dfs3(int from,int fa) {//找环 vis[from]=1; for(int i=0;i<a[from].size();i++) { int to=a[from][i]; if(to==fa) continue ; if(vis[to]) { flag=1; cir1[to]=1; cir1[from]=1; u1[++cnt]=from; v1[cnt]=to; return ; } dfs3(to,from); if(flag&&cir1[to]) if(cir1[from]) { flag=0; u1[++cnt]=from; v1[cnt]=to; return ; } else { cir1[from]=1; u1[++cnt]=from; v1[cnt]=to; return ; } } } int check() {//比较更优方案 for(int i=1;i<=n;i++) { if(res[i]<ans[i]) return 1; else if(res[i]>ans[i]) return 0; } return 0; } void update() {//更新答案 for(int i=1;i<=n;i++) { ans[i]=res[i]; } } int main() { scanf("%d%d",&n,&m); int u,v; for(int i=1;i<=m;i++) { scanf("%d%d",&u,&v); a[u].push_back(v);//vector选手铁锤妹妹 a[v].push_back(u); e[i].from=u; e[i].to=v; } for(int i=1;i<=n;i++) sort(a[i].begin(),a[i].end()); if(m==n) { dfs3(1,0);//找环 int flag=1; for(int i=1;i<=cnt;i++) { du=u1[i];dv=v1[i];//枚举删去环上哪条边 memset(vis,0,sizeof(vis)); tot=0; dfs1(1,0); if(tot<n) continue ; if(flag) { update(); flag=0; } if(check()) update(); } for(int i=1;i<=n;i++) { printf("%d ",ans[i]); } } else { dfs2(1,0); for(int i=1;i<=n;i++) { printf("%d ",ans[i]); } } return 0; }
代码转自铁锤妹妹,注释窝加的
接下来我们谈谈博主清奇的脑洞。
考虑从环入手选择最优解。
先来看最简单的环。
最优解当然是1 2 3 4 5辣。那我们究竟是怎么找出这个顺序的呢?
首先按照60pts的思路,走编号最小的点。走到2。下一个是5,但是如果我们此时回溯到3,走3-->4-->5这条路,所得的字典序会更小。由此可以得到一个贪心思路:在向编号小的点a走的同时,记录下编号较大的点b的编号。当dfs到一个比b编号大且在环上的点时,回溯到b,由b走过去。
我们在记录参数(b的编号)(以下称之为cs)的时候,是在环的“根”处(也就是图中的1节点)记录的,所以要先找个环并且记录环的“根”。
现在把这个环挂到树上。
最优解是1 2 6 4 3 5 7。我们发现在遍历2的子树时一定要走过7.此时就无法回到3然后从3走了。因此我们对cs要有所改变。(cs初始化为inf)
cs更新原则:
如果当前点now是环的“根”root,则cs为它的子树中,在环上且编号较大的那个点
如果当前点在环上但不是root,且cs不是inf。记录它在环上的子树的编号c,找到最大的不在环上的且大于c的子树编号。如果没有,则cs不变,如果有,cs更新。
为什么要cs不是inf才能更新呢?因为如果cs是inf且在环上,说明现在是从编号较大的点走过来的,不需要再判断是否回溯。
找环+对环的dfs:
int rt,er;//rt就是上文中的root,er记录在找环时是否回溯到了root void huan(int now,int fa) { vis[now]++; if(vis[now]>1) {hua[now]=1;rt=now;er++;return ;} if(!head[now])return ; bool bj=0;//记录是否有子树在环上 for(int e=head[now];e;e=ed[e].nxt) { int v=ed[e].to; if(v==fa)continue; if(now==rt)break; huan(v,now); if(hua[v])bj=1; } if(bj)hua[now]=1;//如果有子树在环上,那么now很可能也在环上,特殊情况由下面判断 if(er==2)hua[now]=0;//er==2说明已经回到了root的父亲节点(祖先节点) if(now==rt)er++; } void dfs2(int now,int fa,int cs) { if(now>cs)return ;//该回溯了 if(vis[now])return ; vis[now]=1; vector<int> qwq; ans1[++t]=now;//记录答案 for(int e=head[now];e;e=ed[e].nxt) { int v=ed[e].to; if(v==fa)continue; qwq.push_back(v); } sort(qwq.begin(),qwq.end());//依旧是排序觉得子树的顺序 int qaq=qwq.size(); if(now==rt)//在root处开始记录cs for(int i=qaq-1;i>=0;i--) if(hua[qwq[i]]){cs=qwq[i];break;} if(now!=rt&&hua[now]&&cs!=inf) { int rwr=inf; for(int i=qaq-1;i>=0;i--) if(hua[qwq[i]]){rwr=qwq[i];break;}//rwr记录子树中在环上的点的编号 for(int i=qaq-1;i>=0;i--) if(!hua[qwq[i]]&&qwq[i]>rwr){cs=qwq[i];break;} } for(int i=0;i<qaq;i++) { if((qwq[i]<cs&&now==rt)||(now!=rt&&hua[qwq[i]])) dfs2(qwq[i],now,cs);//如果是开始走编号较小的点或者说now在环上则要带着cs(由于走较大的点的编号的情况在下面更新了cs,所以这么写也是可以的) else dfs2(qwq[i],now,inf);//在走较大的点的时候把cs更新掉 } }
接下来就是愉快的AC了然后博主发现自己的做法好像比较清奇,于是去交了数据加强版,发现也A了