【NOIP2018提高组D2T1】旅行 题解

13 篇文章 0 订阅
9 篇文章 0 订阅

【NOIP2018提高组D2T1】旅行 题解

题目

这里

解题思路

关键词:贪心,基环树
考虑 m = n − 1 m=n-1 m=n1的情况,直接按结点编号大小排序,每次选最小的即可。
s o n i son_i soni表示 i i i的儿子,那么时间复杂度为
O ( ∑ i = 1 n s o n i log ⁡ 2 s o n i ) \begin{aligned}O(\sum_{i=1}^n{son_i\log_2^{son_i}})\end{aligned} O(i=1nsonilog2soni)
所以为 O ( n log ⁡ 2 n ) O(n\log_2^n) O(nlog2n)
m = n m=n m=n时,这是一棵基环树,基环树的套路就是删边,然后按照上面的方法做一遍。
可以发现删掉的边一定在环上,所以并查集找环,然后暴力求最小的即可。
时间复杂度为 O ( n 2 log ⁡ 2 n ) O(n^2\log_2^n) O(n2log2n)

code

用了许多函数。。。

#include<bits/stdc++.h>
#define N 5005
using namespace std;
int n,m,ans[N],tot;
int s[N],c[N][N],fa[N],pt[N],p[N],vis[N];
int la[N],ne[N<<1],to[N<<1],use[N<<1],cnt;
struct In{
	int x,y;
}in[N];
void dfs(int x,int fa){
    s[++tot]=x;
    for(int i=la[x];i;i=ne[i])
    	if(to[i]!=fa&&use[i]==0)
    		dfs(to[i],x);
}
void bl(int x,int en){
	if(x==en&&p[0]==0){
		for(int i=1;i<=pt[0];i++) p[++p[0]]=pt[i];
		return;
	}
	vis[x]=1;
	for(int i=la[x];i;i=ne[i])
		if(!vis[to[i]]&&use[i]==0){
			pt[++pt[0]]=to[i];
			bl(to[i],en);
			pt[0]--;
		}
}
void addnew(int x,int y){
	to[++cnt]=y;
	ne[cnt]=la[x];
	la[x]=cnt;
}
void cancel(int x,int y){
	for(int i=la[x];i;i=ne[i]) 
		if(to[i]==y)
			use[i]=1;
	for(int i=la[y];i;i=ne[i])
		if(to[i]==x)
			use[i]=1;
}
void recovery(int x,int y){
	for(int i=la[x];i;i=ne[i]) 
		if(to[i]==y)
			use[i]=0;
	for(int i=la[y];i;i=ne[i])
		if(to[i]==x)
			use[i]=0;
}
int find(int x){
	if(x==fa[x]) return x;
	else return fa[x]=find(fa[x]);
}
void copy(){
	for(int j=1;j<=n;j++) ans[j]=s[j];
}
void comp(){
	for(int j=1;j<=n;j++)
		if(s[j]<ans[j]){
			copy();
			return;
		}
		else if(s[j]>ans[j]) return;
	return;
}
void disconnect(int x,int y){
	cancel(x,y);
	dfs(1,0);
	comp();
	recovery(x,y);
}
int main(){
    freopen("travel.in","r",stdin);
    freopen("travel.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        int x,y;
        scanf("%d%d",&x,&y);
        c[x][++c[x][0]]=y;
        c[y][++c[y][0]]=x;
        in[i].x=x;
        in[i].y=y;
    }
    for(int i=1;i<=n;i++){
    	int len=c[i][0];
    	sort(c[i]+1,c[i]+len+1);
    	fa[i]=i;
		for(int j=c[i][0];j;j--) addnew(i,c[i][j]);
	}
    if(m==n-1){
    	
    	dfs(1,0);
    	copy();
	}
    else{
		int tx=0,ty=0;
		for(int i=1;i<=m;i++){
			int rtx=find(in[i].x);
			int rty=find(in[i].y);
			if(rtx==rty){
				tx=in[i].x;
				ty=in[i].y;
				break;
			}
			else fa[rtx]=rty;
		}
		pt[++pt[0]]=tx;
		cancel(tx,ty);	
		bl(tx,ty);
		memset(use,0,sizeof(use));
        for(int i=1;i<=n;i++) ans[i]=n+1;
		disconnect(tx,ty);
		for(int i=1;i<p[0];i++){
			memset(s,0,sizeof(s));
			tot=0;
			disconnect(p[i],p[i+1]);
		}
    }
    for(int i=1;i<=n;i++) printf("%d ",ans[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值