NOIP 2018 提高组 复赛 第二天 第一题 旅行 travel AC代码+60分代码(深搜dfs)
总目录详见:NOIP 提高组 复赛 试题 目录 信奥 历年
在线测评地址:https://www.luogu.com.cn/problem/P5022
基环树
基环树就是有n个点n条边的图,由于比树只出现了一个环,那么就称之为基环树了。
基环树结构仍然很简单,但比树要恶心得多一些。
1.AC代码
拓扑排序
处理无向图
可以找出环上的所有点。
之后入度>=2的点就是环上的点
断环法
每次断开环上的一条边跑一遍答案,然后取最大值。
适用于:数据较小,且环不会影响答案的题目
解题思路
这道题有可能是个基环树 我们先不考虑基环树。我们可以发现每次走字典序小的点就好了。我们可以排序一下就可以做到这点。
之后我们考虑基环树,我们暴力删边将基环树变为一棵普通的树。然后计算答案。
时间复杂度: O(n^2)
然后愉快的发现可以过
AC代码如下:
#include <cstdio>
#include <algorithm>
#define maxn 5010
using namespace std;
struct node{
int to,next;
}e[maxn<<1];
struct node2{
int u,v;
}a[maxn<<1];
int n,m,head[maxn],tot,rd[maxn],circle[maxn],cnt,ans[maxn],an,state[maxn],st,q[maxn],h,t;
bool map[maxn][maxn];
int cmp(node2 a,node2 b){
return a.v>b.v;
}
void add_edge(int u,int v){
tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot,rd[v]++;
}
void dfs(int u,int fa){
int b,v;
state[++st]=u;
for(b=head[u];b;b=e[b].next){
v=e[b].to;
if(!map[u][v]&&v!=fa)dfs(v,u);
}
}
void topsort(){//拓扑去除环外的节点
int i,u,v,b;
h=t=1;
for(i=1;i<=n;i++)
if(rd[i]==1)q[t]=i,t++;
while(h<t){
u=q[h],rd[u]=0;
for(b=head[u];b;b=e[b].next){
v=e[b].to;
rd[v]--;
if(rd[v]==1)q[t]=v,t++;
}
h++;
}
}
void find(){//找环//记录环的每个点
int i,x,b,v;
for(i=1;i<=n;i++)
if(rd[i]>1){x=i;break;}//找环的切入点
do{//找环
circle[++cnt]=x;
rd[x]=1;
for(b=head[x];b;b=e[b].next){
v=e[b].to;
if(rd[v]>1){x=v;break;}
}
}while(b);
circle[++cnt]=i;
}
void judge(){//判断是否为更小字典序
int flag=0,i;
for(i=1;i<=n;i++)
if(state[i]<ans[i]){flag=1;break;}
else if(state[i]>ans[i]) return;//此句关键
if(!flag)return;
for(;i<=n;i++)ans[i]=state[i];
}
void solve(){
int i;
for(i=1;i<cnt;i++){//枚举删除的边
map[circle[i]][circle[i+1]]=map[circle[i+1]][circle[i]]=1;
st=0;
dfs(1,0);
judge();
map[circle[i]][circle[i+1]]=map[circle[i+1]][circle[i]]=0;
}
for(i=1;i<=n;i++)printf("%d ",ans[i]);
}
int main(){
int i,u,v;
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++){
scanf("%d%d",&u,&v);
a[i].u=u,a[i].v=v;
a[i+m].u=v,a[i+m].v=u;
}
sort(a+1,a+1+2*m,cmp);
for(i=1;i<=2*m;i++)
add_edge(a[i].u,a[i].v);
if(m==n-1){//普通的树
dfs(1,0);
for(i=1;i<=n;i++)printf("%d ",state[i]);
return 0;
}
for(i=1;i<=n;i++)ans[i]=5010;
topsort();
find();
solve();
return 0;
}
2.60分代码(深搜dfs)
针对如下数据进行编码
很明显,是一棵树,从城市1开始遍历,在树上遍历时,只需要注意遍历当前城市的孩子时,将孩子先遍历出来,按自小到大排序,再进行下一步的遍历即可。
60分代码(深搜dfs)如下
#include <cstdio>
#include <algorithm>
#define maxn 5010
using namespace std;
struct node{
int to,next;
}e[maxn<<1];
int n,m,head[maxn],tot;
void add_edge(int u,int v){
tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot;
}
void dfs(int u,int fa){
int b,v,i;
int a[maxn],cnt=0;
printf("%d ",u);
for(b=head[u];b;b=e[b].next){
v=e[b].to;
if(v!=fa)cnt++,a[cnt]=v;
}
sort(a+1,a+1+cnt);
for(i=1;i<=cnt;i++){
dfs(a[i],u);
}
}
int main(){
int i,u,v;
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++){
scanf("%d%d",&u,&v);
add_edge(u,v),add_edge(v,u);
}
dfs(1,0);
return 0;
}
60分代码(深搜dfs)改进如下
#include <cstdio>
#include <algorithm>
#define maxn 5010
using namespace std;
struct node{
int to,next;
}e[maxn<<1];
struct node2{
int u,v;
}a[maxn<<1];
int n,m,head[maxn],tot;
int cmp(node2 a,node2 b){
return a.v>b.v;
}
void add_edge(int u,int v){
tot++,e[tot].to=v,e[tot].next=head[u],head[u]=tot;
}
void dfs(int u,int fa){
int b,v;
printf("%d ",u);
for(b=head[u];b;b=e[b].next){
v=e[b].to;
if(v!=fa)dfs(v,u);
}
}
int main(){
int i,u,v;
scanf("%d%d",&n,&m);
for(i=1;i<=m;i++){
scanf("%d%d",&u,&v);
a[i].u=u,a[i].v=v;
a[i+m].u=v,a[i+m].v=u;
}
sort(a+1,a+1+2*m,cmp);
for(i=1;i<=2*m;i++)
add_edge(a[i].u,a[i].v);
dfs(1,0);
return 0;
}