P5022 旅行

本文介绍了一种求解树或基环树遍历字典顺最小方案的方法。对于树,采用直接贪心策略;对于基环树,先定位并删除环中某边,再应用树策略,最后对比所有可能方案,输出最优解。

给定一个树/基环树,求遍历字典顺最小的方案

当原图是一棵树的时候,可以直接贪心的进行操作

当原图是一棵基环树的时候,我们先找到树上的环,然后枚举环上的一条边,将其删去,剩下的就和树上的操作一样了,最后将所有得到的方案进行比较,输出最小的即可

 

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=5005;
int n,m,tot,vis[maxn],out[maxn],ans[maxn];
vector <int> G[maxn];
int read()
{
	int x=0,f=1;
	char cc=getchar();
	while((cc<'0' || cc>'9') && cc!='-') cc=getchar();
	if(cc=='-') f=-1,cc=getchar();
	while(cc>='0' && cc<='9')
	{
		x=x*10+cc-'0';
		cc=getchar();
	}
	return x*f;
}
void dfs(int x,int fa)
{
	ans[++tot]=x;
	vis[x]=1;
	for(int k=0;k<G[x].size();k++)
	{
		int to=G[x][k];
		if(to==fa) continue;
		if(!vis[to]) dfs(to,x);
	}
}
int cnt,num,delu,delv;
struct edge
{
	int u,v;
}anss[maxn];
bool circle(int x,int fa)
{
	if(num) return 0;
	vis[x]=1;
	for(int k=0;k<G[x].size();k++)
	{
		int to=G[x][k];
		if(to==fa) continue;
		if(vis[to])
		{
			num=to;
			anss[++cnt]=(edge){x,to};
			return 1;
		}
		if(circle(to,x))
		{
			if(to==num) return 0;
			else
			{
				anss[++cnt]=(edge){x,to};
				return 1;
			}
		}
	}
	
}
bool check(int x,int y)
{
	if(x==delu && y==delv) return 0;
	if(y==delu && x==delv) return 0;
	return 1;
}
void dfss(int x,int fa)
{
	ans[++tot]=x;
	vis[x]=1;
	for(int k=0;k<G[x].size();k++)
	{
		int to=G[x][k];
		if(to==fa) continue;
		if(!vis[to] && check(x,to)) dfss(to,x);
	}
}
bool cmp()
{
	for(int i=1;i<=tot;i++)
	{
		if(ans[i]>out[i]) return 0;
		if(ans[i]<out[i]) return 1;
	}
	return 1;
}
int main()
{
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
	n=read(); m=read();
	int x,y;
	for(int i=1;i<=m;i++)
	{
		x=read(); y=read();
		G[x].push_back(y);
		G[y].push_back(x);
	}
	if(m==n-1)
	{
		for(int i=1;i<=n;i++)
			sort(G[i].begin(),G[i].end());
		dfs(1,0);
		for(int i=1;i<=n;i++)
			printf("%d ",ans[i]);
		return 0;	
	}
	if(n==m)
	{
		memset(out,0x3f,sizeof(ans));
		for(int i=1;i<=n;i++)
			sort(G[i].begin(),G[i].end());
		circle(1,0);
		for(int i=1;i<=cnt;i++)
		{
			tot=0;
			memset(vis,0,sizeof(vis));
			delu=anss[i].u; delv=anss[i].v;
			dfss(1,0);
			if(cmp() && tot==n) memcpy(out,ans,sizeof(ans));
		}
		for(int i=1;i<=n;i++) printf("%d ",out[i]);
	} 
	return 0;
}

 

# P5022 [NOIP 2018 提高组] 旅行 ## 题目背景 NOIP2018 提高组 D2T1 ## 题目描述 小 Y 是一个爱好旅行的 OIer。她来到 X 国,打算将各个城市都玩一遍。 小 Y 了解到,X 国的 $n$ 个城市之间有 $m$ 条双向道路。每条双向道路连接两个城市。 不存在两条连接同一对城市的道路,也不存在一条连接一个城市和它本身的道路。并且, 从任意一个城市出发,通过这些道路都可以到达任意一个其他城市。小 Y 只能通过这些 道路从一个城市前往另一个城市。 小 Y 的旅行方案是这样的:任意选定一个城市作为起点,然后从起点开始,每次可 以选择一条与当前城市相连的道路,走向一个没有去过的城市,或者沿着第一次访问该 城市时经过的道路后退到上一个城市。当小 Y 回到起点时,她可以选择结束这次旅行或 继续旅行。需要注意的是,小 Y 要求在旅行方案中,每个城市都被访问到。 为了让自己的旅行更有意义,小 Y 决定在每到达一个新的城市(包括起点)时,将 它的编号记录下来。她知道这样会形成一个长度为 $n$ 的序列。她希望这个序列的字典序 最小,你能帮帮她吗? 对于两个长度均为 $n$ 的序列 $A$ 和 $B$,当且仅当存在一个正整数 $x$,满足以下条件时, 我们说序列 $A$ 的字典序小于 $B$。 - 对于任意正整数 $1 ≤ i < x$,序列 $A$ 的第 $i$ 个元素 $A_i$ 和序列 $B$ 的第 $i$ 个元素 $B_i$ 相同。 - 序列 $A$ 的第 $x$ 个元素的值小于序列 $B$ 的第 $x$ 个元素的值。 ## 输入格式 输入文件共 $m + 1$ 行。第一行包含两个整数 $n,m(m ≤ n)$,中间用一个空格分隔。 接下来 m 行,每行包含两个整数 $u,v (1 ≤ u,v ≤ n)$ ,表示编号为 $u$ 和 $v$ 的城市之 间有一条道路,两个整数之间用一个空格分隔。 ## 输出格式 输出文件包含一行,$n$ 个整数,表示字典序最小的序列。相邻两个整数之间用一个 空格分隔。 ## 输入输出样例 #1 ### 输入 #1 ``` 6 5 1 3 2 3 2 5 3 4 4 6 ``` ### 输出 #1 ``` 1 3 2 5 4 6 ``` ## 输入输出样例 #2 ### 输入 #2 ``` 6 6 1 3 2 3 2 5 3 4 4 5 4 6 ``` ### 输出 #2 ``` 1 3 2 4 5 6 ``` ## 说明/提示 【数据规模与约定】 对于 $100\%$ 的数据和所有样例, $1 \le n \le 5000 $ 且 $m = n − 1$ 或 $m = n$ 。 对于不同的测试点, 我们约定数据的规模如下: ![](https://cdn.luogu.com.cn/upload/pic/43271.png) 下面代码能否解决上述题目 #include <bits/stdc++.h> using namespace std; const int N=5e3+10; int n,m,idx,ind[N]; bool vis[N],vis1[N],frun=true; vector<int>G[N],ans,h,th; queue<int>q; bool init(int u) { for(int i=0,l=th.size(); i<l; i++) { if(u==th[i]) return true; } return false; } void mmp() { for(int i=1; i<=n; i++) { sort(G[i].begin(),G[i].end()); } } void topo() { for(int i=1; i<=n; i++) { if(ind[i]==1) q.push(i); } while(q.size()) { int x=q.front() ; q.pop(); vis1[x]=1; for(int i=0,len=G[x].size(); i<len ; i++) { ind[G[x][i]]--; if(ind[G[x][i]]==1) q.push(G[x][i]); } } for(int i=1; i<=n; i++) { if(!vis1[i]) th.push_back(i); } memset(vis1,0,sizeof(vis1)); } void dfs1(int u) { vis1[u]=true; if(init(u)) { idx=u; vis1[u]=false; frun=false; } for(int i=0,l=G[u].size(); i<l; i++) { if(frun==false) return; int v=G[u][i]; if(init(v)) { idx=v; frun=false; break; } if(!vis1[v]) dfs1(v); } } void dfs2(int u) { vis1[u]=1; h.push_back(u); for(int i=0,l=G[u].size(); i<l; i++) { if(h.size()==th.size()) return; int v=G[u][i]; if(init(v)&&(!vis1[v])) { dfs2(v); } } } void cutmp() { bool f=false; int cutu,cutv; int l=h.size(); if(h[1]>h[l-1]) { reverse(h.begin()+1,h.end()); } for(int i=1; i<l-1; i++) { if(h[i]>h[l-1]) { cutu=h[i-1]; cutv=h[i]; f=true; break; } } if(!f) cutu=h[0],cutv=h[l-1]; for(vector<int>::iterator it=G[cutu].begin(); it!=G[cutu].end(); it++) { if(*it == cutv) { G[cutu].erase(it); sort(G[cutu].begin(),G[cutu].end()); break; } } for(vector<int>::iterator it=G[cutv].begin(); it!=G[cutv].end(); it++) { if(*it == cutu) { G[cutv].erase(it); sort(G[cutv].begin(),G[cutv].end()); break; } } //cout<<cutu<<" "<<cutv<<"\n"; } void dfs(int u) { vis[u]=1; ans.push_back(u); for(int i=0,l=G[u].size(); i<l; i++) { if(!vis[G[u][i]]) dfs(G[u][i]); } } int main() { /* freopen("P5022_20.in","r",stdin); freopen("P5022_20.ans","w",stdout); */ ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin>>n>>m; for(int i=0,u,v; i<m; i++) { cin>>u>>v; G[u].push_back(v); G[v].push_back(u); ind[u]++,ind[v]++; } mmp(); if(m==n) { topo(); dfs1(1); dfs2(idx); /* for(int i=0,l=h.size();i<l;i++){ cout<<h[i]<<" "; } cout<<"\n";*/ cutmp(); } dfs(1); for(int i=0,l=ans.size(); i<l; i++) { cout<<ans[i]<<" "; } return 0; }
最新发布
03-13
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值