关于基环树
关于基数环
含义
啥事基数环呢?
简单来说就是树上在加一条边。
它形如一个环,环上每个点都有一棵子树的形式。
因此,对基环树的处理大部分就是对树处理和对环处理。
显然,难度在于后者。
求环
不难理解,复杂度 \(O(n)\)
struct edge {
int nxt,to,w;
}e[N<<1];
int tot, head[N];
int vis[N],cnt,ring[N],dist[N],inring[N],rt;
int getring(int x,int fa){
if(vis[x]){
rt=x;
return 1;
}
vis[x]=1;
int tmp;
for(int i=head[x];i;i=e[i].nxt){
int to=e[i].to;
if(to==fa) continue;
tmp=getring(to,x)
if(tmp){
if(tmp==1){
ring[++cnt]=x;
dist[cnt]=e[i].w;
inring[x]=1;
if(x!=rt) return 1;
}
return 2;
}
}
return 0;
}
接下来就是做题了...
P5022 [NOIP2018 提高组] 旅行
分析
注意到数据点并奀,且前 \(16\) 个测试点满足 \(m=n-1\),也就是一棵树,果断 DFS 拿 60pts 跑路
你别说我还真写了...
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rd read()
#define Elaina 0
inline int read(){
int f=1,x=0;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) f=(ch=='-'?-1:1);
for(;isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
return f*x;
}
const int N=5010;
const int inf=INT_MAX;
vector<int> vec[N];
int n,m;
int ans[N],cnt;
int vis[N];
void dfs(int x,int fa){
if(vis[x]) return ;
vis[x]=1;
ans[++cnt]=x;
for(auto i:vec[x]){
if(i==fa) continue;
dfs(i,x);
}
}
signed main(){
n=rd,m=rd;
int u,v;
for(int i=1;i<=m;i++){
u=rd,v=rd;
vec[u].push_back(v);
vec[v].push_back(u);
}
for(int i=1;i<=n;i++)
sort(vec[i].begin(),vec[i].end());
dfs(1,0);
for(int i=1;i<=cnt;i++){
printf("%lld ",ans[i]);
}
return Elaina;
}
咳咳...
后面几个点满足 \(m=n\) 也就是一颗基环树。
由于基环树就是在树上多加了一条边,所以我们暴力枚举要拆下的一条边即可。
还是比较水的...
Code
Elaina's Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rd read()
#define Elaina 0
inline int read(){
int f=1,x=0;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) f=(ch=='-'?-1:1);
for(;isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
return f*x;
}
const int N=1e5+100;
vector<int> vec[N];
int n,m,res[N],ans[N],tot,idx,vis[N];
int dfrm,dto;
struct EDGE{
int from,to;
}e[N<<1];
void dfs2(int x,int fa){
res[++tot]=x;
for(auto i:vec[x]){
if(i==fa||vis[i]||(x==dfrm&&i==dto)||(x==dto&&i==dfrm)) continue;
vis[i]=1;
dfs2(i,x);
}
}
void dfs1(int x,int fa){
ans[++tot]=x;
for(auto i:vec[x]){
if(i==fa||vis[i]) continue;
vis[i]=1;
dfs1(i,x);
}
}
bool check(){
for(int i=1;i<=n;i++){
if(ans[i]>res[i]) return 1;
else if(ans[i]<res[i]) return 0;
}
return 0;
}
signed main(){
n=rd,m=rd;
int x,y;
for(int i=1;i<=m;i++){
x=rd,y=rd;
vec[x].push_back(y);
vec[y].push_back(x);
e[i]={x,y};
}
for(int i=1;i<=n;i++) sort(vec[i].begin(),vec[i].end());
if(m==n){
bool fg=1;
for(int i=1;i<=n;i++){
dfrm=e[i].from,dto=e[i].to;
for(int j=1;j<=n;j++) vis[j]=0,res[j]=0;
tot=0;
vis[1]=1;
dfs2(1,0);
if(tot<n) continue;
if(fg){
fg=0;
for(int i=1;i<=n;i++){
ans[i]=res[i];
}
}
if(check()){
for(int i=1;i<=n;i++){
ans[i]=res[i];
}
}
}
}else{
vis[1]=1;
dfs1(1,0);
}
for(int i=1;i<=n;i++){
printf("%lld ",ans[i]);
}
return Elaina;
}
/*
6 6
1 2
1 3
1 4
1 5
1 6
2 3
*/
P5049 [NOIP2018 提高组] 旅行 加强版
分析
交上一份代码显然会T掉 不T还叫什么加强版啊喂
守丸一下可以发现,当我们在环上走的时候,只有其出边为环上的那个点编号最大,且比回溯后第一个走的点还大,这时候才回溯,其他时候就正常跑DFS。
代码实现上可以用一个 \(flag\) 来标记是否需要回溯,用 \(mx\) 记录当前节点中第一个比环上的出边那个节点还要大的节点,以便后面判断是否回溯时比较。
代码有猪食()
Code
Elaina's Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define rd read()
#define Elaina 0
inline int read(){
int f=1,x=0;
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) f=(ch=='-'?-1:1);
for(;isdigit(ch);ch=getchar()) x=(x<<3)+(x<<1)+ch-'0';
return f*x;
}
const int N=500010;
const int inf=0x7fffffff;
int n,m,vis[N],ans[N],cnt,f[N],ring[N],flag,mx,is_trackback,head[N],idx;
struct EDGE{
int nxt,to;
}e[N<<1];
void add(int x,int y){
e[++idx].to=y;
e[idx].nxt=head[x];
head[x]=idx;
}
struct Node{
int frm,to;
}node[N<<1];
bool cmp(Node x,Node y){
return x.to>y.to;
}
void dfs1(int x,int fa){
if(vis[x]) return ;
vis[x]=1;
ans[++cnt]=x;
for(int i=head[x];i;i=e[i].nxt){
int to=e[i].to;
if(to==fa) continue;
dfs1(to,x);
}
}
void GetRing(int x,int fa){//找出图中的环
if(flag) return;
if(!f[x]) f[x]=fa;
else if(f[x]!=fa){
while(x!=fa){
ring[fa]=1;
fa=f[fa];
}
ring[x]=1;
flag=1;
return;
}
for(int i=head[x];i;i=e[i].nxt){
int to=e[i].to;
if(to==fa) continue;
GetRing(to,x);
}
}
void dfs2(int x,int fa){
if(vis[x]) return;
vis[x]=1;
ans[++cnt]=x;
if(ring[x]){//如果在环上
int flag=0;
for(int i=head[x];i;i=e[i].nxt){
if(is_trackback) break;//temp标记是否回溯过
int to=e[i].to;
if(ring[to]&&!vis[to]){
i=e[i].nxt;
while(vis[e[i].to]) i=e[i].nxt;//跳过已经被访问的节点
if(i) mx=e[i].to;//i不为0即环上的出边不是最大的出边,mx记录第一个比环的出边大的那个点
else if(to>mx){//环上的出边是最大的出边且比我们回溯后第一次要走的节点还大
flag=1,is_trackback=1;//需要回溯
}
break;
}
}
for(int i=head[x];i;i=e[i].nxt){
int to=e[i].to;
if(ring[to] && flag) continue;//flag=1 需要回溯,不再继续往下走
dfs2(to,x);
}
}else{
for(int i=head[x];i;i=e[i].nxt){
int to=e[i].to;
if(to==x) continue;
dfs2(to,x);
}
}
}
signed main(){
n=rd,m=rd;
int u,v;
for(int i=1;i<=m;i++){
u=rd,v=rd;
node[i]={u,v};
node[i+m]={v,u};
}
sort(node+1,node+1+2*m,cmp);
for(int i=1;i<=2*m;i++){
add(node[i].frm,node[i].to);
}
if(m==n-1){
dfs1(1,0);
}else{
GetRing(1,1);
flag=0;
mx=inf;
dfs2(1,0);
}
for(int i=1;i<=cnt;i++){
printf("%lld ",ans[i]);
}
return Elaina;
}
图
\[\color{red}{\LARGE 我永远喜欢满穗!}
\]