定义:若一张无向连通图不存在割点,则称它为"点双连通图"。若一张无向连通图不存在割边,则称它为"边双连通图"。
无向图图的极大点双连通子图被称为"点双连通分量",记为 " v − D C C " "v−DCC" "v−DCC"。无向图图的极大边双连通子图被称为**“边双连通分量”**,记为 " e − D C C " 。 "e−DCC"。 "e−DCC"。
边双连通分量
核心概念:没有割边的无向连通图。
注意到,割边只会把图分成两部分,对图中的点没有影响。那么有一个显而易见的求法就是:利用Tarjan算法求解无向图的割边,并将割边去除,得到的各个连通块即为边双连通分量。
模 板 : 模板: 模板:
void Tarjan(int x,int fa)
{
dfn[x]=low[x]=++Time;
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(y==fa) continue;
if(!dfn[y])
{
Tarjan(y,x);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x]) bri[i]=bri[i^1]=true;
}
else low[x]=min(low[x],dfn[y]);
}
}
void dfs(int x) // 相当于对每个双连通分量染色
{
col[x]=cnt;
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(bri[i]) continue;
if(!col[y]) dfs(y);
}
}
其实 T a r j a n Tarjan Tarjan后也可以不用对每个连通块进行染色, l o w low low数组相当于已经染色了, l o w low low相同则两点在同一双连通分量
图删去一条边之后不影响连通性,就是双连通图的概念了,这题就是要让你添加最少的边使图变成双连通图,做法就是先找出所有边双联通块(同一个双连通块内的点的low值是相等的),把连通块看成一个点,整张图就变成一颗树,问题转化成如何把这棵树变成双连通图,扫一遍统计所有结点的度数,度数为1的结点就需要加边,加边优先和其他度数为1的点相连,最后需要添加的边就是(度数为1的点的数量+1)/2。
#include <set>
#include <map>
#include <list>
#include <cmath>
#include <stack>
#include <queue>
#include <string>
#include <bitset>
#include <vector>
#include<cstring>
#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define lowbit(x) ((x)&-(x))
typedef unsigned long long ull;
#define freedom() ios::sync_with_stdio(false);
inline int read(){int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();return s*w;}
const int maxn = 1e4+5;
int tot,head[maxn];
struct Edge{
int to,next;
}e[maxn*10];
int cnt,col[maxn];
int dfn[maxn],low[maxn],Time;
void add_e(int u,int v)
{
e[tot]={v,head[u]};
head[u]=tot++;
}
bool bri[maxn*10];
int ans;
void Tarjan(int x,int fa)
{
dfn[x]=low[x]=++Time;
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(y==fa) continue;
if(!dfn[y])
{
Tarjan(y,x);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x]) bri[i]=bri[i^1]=true;
}
else low[x]=min(low[x],dfn[y]);
}
}
void dfs(int x)
{
col[x]=cnt;
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(bri[i]) continue;
if(!col[y]) dfs(y);
}
}
int In[maxn];
int solve(int n)
{
ans=0;
for(int i=1;i<=n;i++)
if(!dfn[i]) Tarjan(i,i);
for(int i=1;i<=n;i++)
if(!col[i]) ++cnt,dfs(i);
for(int i=1;i<=n;i++)
for(int k=head[i];k!=-1;k=e[k].next)
{
int u=i,v=e[k].to;
if(col[u]!=col[v]) In[col[v]]++;
}
for(int i=1;i<=cnt;i++)
if(In[i]==1) ans++;
return (ans+1)/2;
}
void init()
{
Time=0,tot=0,cnt=0;
memset(In,0,sizeof(In));
memset(head,-1,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(col,0,sizeof(col));
memset(bri,0,sizeof(bri));
}
int main()
{
int n=read(),m=read();
init();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
add_e(u,v);
add_e(v,u);
}
printf("%d\n",solve(n));
return 0;
}
或者
#include <set>
#include <map>
#include <list>
#include <cmath>
#include <stack>
#include <queue>
#include <string>
#include <bitset>
#include <vector>
#include<cstring>
#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define lowbit(x) ((x)&-(x))
typedef unsigned long long ull;
#define freedom() ios::sync_with_stdio(false);
inline int read(){int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();return s*w;}
const int maxn = 1e4+5;
int tot,head[maxn];
struct Edge{
int to,next;
}e[maxn*10];
int cnt,col[maxn];
int dfn[maxn],low[maxn],Time;
void add_e(int u,int v)
{
e[tot]={v,head[u]};
head[u]=tot++;
}
bool bri[maxn*10];
int ans;
void Tarjan(int x,int fa)
{
dfn[x]=low[x]=++Time;
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(y==fa) continue;
if(!dfn[y])
{
Tarjan(y,x);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x]) bri[i]=bri[i^1]=true;
}
else low[x]=min(low[x],dfn[y]);
}
}
void dfs(int x)
{
col[x]=cnt;
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(bri[i]) continue;
if(!col[y]) dfs(y);
}
}
int In[maxn];
int solve(int n)
{
ans=0;
for(int i=1;i<=n;i++)
if(!dfn[i]) Tarjan(i,i);
for(int i=1;i<=n;i++)
if(!col[i]) ++cnt,dfs(i);
for(int i=1;i<=n;i++)
for(int k=head[i];k!=-1;k=e[k].next)
{
int u=i,v=e[k].to;
if(low[u]!=low[v]) In[low[v]]++;
}
for(int i=1;i<=n;i++)
if(In[i]==1) ans++;
return (ans+1)/2;
}
void init()
{
Time=0,tot=0,cnt=0;
memset(In,0,sizeof(In));
memset(head,-1,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(col,0,sizeof(col));
memset(bri,0,sizeof(bri));
}
int main()
{
int n=read(),m=read();
init();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
add_e(u,v);
add_e(v,u);
}
printf("%d\n",solve(n));
return 0;
}
对于树,我们显然使用树上点距的方法来求解。dis(x,y)=depthx+depthy−2depthlca(x,y)。lca用树上倍增求解就可以了,这些都是模板,就不过多讨论了。
那么这是无向图呢,e−DCC缩点构树就可以了。
为什么不用v−DCC?v−DCC比较特殊,由于割点同时被多个点双连通分量包含,所以缩点需要保留割点,会增加点的数量,一般模型中,我们使用e−DCC缩点。
#include <set>
#include <map>
#include <list>
#include <cmath>
#include <stack>
#include <queue>
#include <string>
#include <bitset>
#include <vector>
#include<cstring>
#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define lowbit(x) ((x)&-(x))
typedef unsigned long long ull;
#define freedom() ios::sync_with_stdio(false);
inline int read(){int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();return s*w;}
const int maxn = 5e5+5;
int tot,head[maxn];
int Head[maxn],Tot;
struct Edge{
int to,next;
}e[maxn],E[maxn];
int pre[maxn],id[maxn];
int cnt,col[maxn];
int U[maxn],V[maxn];
int dis[maxn],lca[maxn];
bool vis[maxn],bri[maxn<<2];
int dfn[maxn],low[maxn],Time;
void init(){
memset(Head,-1,sizeof(Head));
memset(head,-1,sizeof(head));
}
void add_e(int u,int v,int Id){
e[tot]={v,head[u]};id[tot]=Id;head[u]=tot++;
}
void add_E(int u,int v){
E[Tot]={v,Head[u]};Head[u]=Tot++;
}
void Tarjan(int x,int fa)
{
dfn[x]=low[x]=++Time;
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(y==fa) continue;
if(!dfn[y])
{
Tarjan(y,x);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x]) bri[i]=bri[i^1]=true;
}
else low[x]=min(low[x],dfn[y]);
}
}
void dfs(int x)
{
col[x]=cnt;
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(bri[i]) continue;
if(!col[y]) dfs(y);
}
}
int find(int x)
{
if(x!=pre[x]) pre[x]=find(pre[x]);
return pre[x];
}
void TarjanLca(int x)
{
vis[x]=true;
pre[x]=x;
for(int i=Head[x];i!=-1;i=E[i].next)
{
int y=E[i].to;
if(vis[y]) continue;
dis[y]=dis[x]+1;
TarjanLca(y);
pre[y]=x;
}
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to,Id=id[i];
if(vis[y]) lca[Id]=find(y);
}
}
void solve(int n)
{
for(int i=1;i<=n;i++)
if(!dfn[i]) Tarjan(i,i);
for(int i=1;i<=n;i++)
if(!col[i]) ++cnt,dfs(i);
for(int i=1;i<=n;i++)
for(int k=head[i];k!=-1;k=e[k].next)
{
int u=i,v=e[k].to;
if(col[u]!=col[v]) add_E(col[u],col[v]),add_E(col[v],col[u]);
}
}
void Output()
{
int q=read();
tot=0;
memset(head,-1,sizeof(head));
for(int i=1;i<=q;i++)
{
int u=read(),v=read();
U[i]=col[u],V[i]=col[v];
add_e(col[u],col[v],i),add_e(col[v],col[u],i);
}
TarjanLca(1);
for(int i=1;i<=q;i++)
printf("%d\n",dis[U[i]]+dis[V[i]]-2*dis[lca[i]]);
}
int main()
{
int n=read(),m=read();
init();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
add_e(u,v,0);
add_e(v,u,0);
}
solve(n);
Output();
return 0;
}
倍
增
法
倍增法
倍增法
c
o
d
e
:
code :
code:
#include <set>
#include <map>
#include <list>
#include <cmath>
#include <stack>
#include <queue>
#include <string>
#include <bitset>
#include <vector>
#include<cstring>
#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
#define lowbit(x) ((x)&-(x))
typedef unsigned long long ull;
#define freedom() ios::sync_with_stdio(false);
inline int read(){int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();return s*w;}
const int maxn = 2e5+5;
int tot,head[maxn];
int Head[maxn],Tot;
struct Edge{
int to,next;
}e[maxn],E[maxn];
int pre[maxn];
int cnt,col[maxn];
bool vis[maxn];
int dfn[maxn],low[maxn],Time;
int id[maxn]; bool bri[maxn<<2];
void init(){
for(int i=0;i<maxn;i++) Head[i]=head[i]=-1;
}
void add_e(int u,int v){
e[tot]={v,head[u]};head[u]=tot++;
}
void add_E(int u,int v){
E[Tot]={v,Head[u]};Head[u]=Tot++;
}
int n,m;
void Tarjan(int x,int fa)
{
dfn[x]=low[x]=++Time;
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(y==fa) continue;
if(!dfn[y])
{
Tarjan(y,x);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x]) bri[i]=bri[i^1]=true;
}
else low[x]=min(low[x],dfn[y]);
}
}
void dfs(int x)
{
col[x]=cnt;
for(int i=head[x];i!=-1;i=e[i].next)
{
int y=e[i].to;
if(bri[i]) continue;
if(!col[y]) dfs(y);
}
}
int U[maxn],V[maxn];
void solve()
{
for(int i=1;i<=n;i++)
if(!dfn[i]) Tarjan(i,i);
for(int i=1;i<=n;i++)
if(!col[i]) ++cnt,dfs(i);
// for(int i=1;i<=n;i++)
// for(int k=head[i];k!=-1;k=e[k].next)
// {
// int u=i,v=e[k].to;
// if(col[u]!=col[v]) add_E(col[u],col[v]),add_E(col[v],col[u]);
// }
for(int i=1;i<=m;i++)
{
if(col[U[i]]!=col[V[i]])
add_E(col[U[i]],col[V[i]]),add_E(col[V[i]],col[U[i]]);
}
}
int dep[maxn],f[maxn][100],lg[maxn];
void dfss(int x,int fa)
{
f[x][0]=fa,dep[x]=dep[fa]+1;
for(int i=1;i<=lg[dep[x]];i++)
f[x][i]=f[f[x][i-1]][i-1];
// for(int i=1;(1<<i)<=dep[x];i++)
// f[x][i]=f[f[x][i-1]][i-1];
for(int i=Head[x];i!=-1;i=E[i].next)
if(E[i].to!=fa) dfss(E[i].to,x);
}
int LCA(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
while (dep[x]>dep[y]) x=f[x][lg[dep[x]-dep[y]]-1];
if(x==y) return x;
for(int i=lg[dep[x]]-1;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
void Query()
{
for(int i=1;i<=cnt;i++) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
dfss(1,0);
int q=read();
for(int i=1;i<=q;i++)
{
int u=read(),v=read();
printf("%d\n",dep[col[u]]+dep[col[v]]-2*dep[LCA(col[u],col[v])]);
}
}
int main()
{
n=read(),m=read();
init();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
U[i]=u,V[i]=v;
add_e(u,v);
add_e(v,u);
}
solve();
Query();
return 0;
}
点双连通分量#
核心概念:没有割点的无向连通图。
这里给出定理,无向连通图是"点双连通图",当且仅当满足下列两个条件之一:
图的顶点不超过2个。
图中任意两个点都同时包含在一个简单环中。"简单环"指的是不相交的环。
点双连通分量的求法:
若某个点为“孤立点”,这个点肯定是点双。
其他的点双连通分量大小至少为2个点。
与强联通分量类似,用一个栈来维护,如果这个点第一次被访问时,把该节点进栈。当割点判定法则中的条件 dfnx≤lowy时,无论x是否为根,都要从栈顶不断弹出节点,直至节点y被弹出,刚才弹出的所有节点与节点x一起构成一个点双连通分量。
int dfn[maxn],low[maxn],id,t;
edge zhan[(maxn*maxn)<<1];//存边的栈
int belong[maxn],cnt;//belong记录每个点属于哪一个点双,cnt记录点双个数
bool cut[maxn];
set<int> s[maxn];//记录每个点双包含哪些点,如果题目不需要也可以不求
void dfs(int x,int from)
{
dfn[x]=low[x]=++id; int son=0;
for(int i=first[x];i;i=e[i].next)
{
if(i==(from^1))continue;
int y=e[i].y;
if(!dfn[y])
{
zhan[++t]=e[i]; dfs(y,i); son++;//先压栈再遍历
if(low[y]<low[x])low[x]=low[y];
if(low[y]>=dfn[x])//发现x是割点
{
cnt++; edge xx; cut[x]=true;
do{
xx=zhan[t--];//弹出
belong[xx.x]=belong[xx.y]=cnt;//标记
s[cnt].insert(xx.x);s[cnt].insert(xx.y);//记录
}while(xx.x!=x||xx.y!=y);//假如已经弹到 x到y 这条边了,就停下来
}
}
else if(dfn[y]<low[x])low[x]=dfn[y];
}
if(from==-1&&son==1)cut[x]=false;
}
对于每一个发生事故的点,可能会将原图分为几个联通块,使得我们需要在不同的联通块设置更多的救援出口,这就和我们割点模型有些相似了。
进一步地,我们需要求出图中的每一个割点和各个点双连通分量,对于每一个点双连通分量,如果当中包含了一个割点,则说明这当中我们需要在非割点位置设置一个救援出口(如果割点位置发生事故,该连通块会被独立,必须额外独立设置一个救援出口),出口数累加,方案数按照点双连通分量大小减去一(一个割点不能放)累乘即可。如果当中包含了两个割点,则说明无论什么情况这一块都不会被"独立",不需要设置救援出口。
最后,还有一种特殊情况需要判断:若整张图就是一个点双连通分量,即没有割点,那么我们至少设置两个出口(若设置一个,可能恰好在出口处发生事故),方案数为n(n−1)2。
算法流程:
先tarjan求一下所有的点双连通分量。然后对于每一个点双连通分量,分类讨论:
只有一个割点,必须选一个非割点。
有多于1个割点,不用选
没有个割点,必须选两个。
#include <set>
#include <map>
#include <list>
#include <cmath>
#include <stack>
#include <queue>
#include <string>
#include <bitset>
#include <vector>
#include<cstring>
#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
typedef unsigned long long ull;
inline int read(){int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();return s*w;}
const int maxn = 1e3+5;
int tot,head[maxn];
int dnf[maxn],low[maxn],Time;
int n,m,cnt,cas;
stack<int>sk;
bool col[maxn],vis[maxn];
vector<int>vec[1005];
struct Edge{
int to,next;
}edge[maxn<<2];
void add_edge(int u,int v)
{
edge[tot]={v,head[u]};
head[u]=tot++;
}
void init()
{
n=0,tot=0,Time=0,cnt=0;
memset(vis,0,sizeof(vis));
while (!sk.empty()) sk.pop();
memset(head,-1,sizeof(head));
memset(dnf,0,sizeof(dnf));
memset(low,0,sizeof(low));
memset(col,0,sizeof(col));
for(int i=0;i<maxn;i++) vec[i].clear();
}
void Tarjan(int x,int fa)
{
dnf[x]=low[x]=++Time;
sk.push(x);
int Fa=0;
if(x==fa&&head[x]==-1)
{
cnt++;
sk.pop();
vec[cnt].push_back(x);
return;
}
for(int i=head[x];i!=-1;i=edge[i].next)
{
int y=edge[i].to;
if(y==fa) continue;
if(!dnf[y])
{
Tarjan(y,x);
low[x]=min(low[x],low[y]);
if(low[y]>=dnf[x])
{
++cnt;
int top=0;
while(top!=y)
{
top=sk.top();
sk.pop();
vec[cnt].push_back(top);
}
vec[cnt].push_back(x);
Fa++;
if(x!=fa||Fa>1) col[x]=true;
}
}
else low[x]=min(low[x],dnf[y]);
}
}
void solve()
{
ll ans=0,Ans=1;
if(cnt==1) {printf("Case %d: %d %d\n",++cas,2,n*(n-1)/2);return;}
for(int i=1;i<=cnt;i++)
{
int g=0;
for(int k=0;k<vec[i].size();k++)
if(col[vec[i][k]]) g++;
if(g==1) ans+=1,Ans*=(vec[i].size()-1);
else if(g>1) continue;
}
printf("Case %d: %lld %lld\n",++cas,ans,Ans);
}
void Input()
{
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
if(!vis[u]) n++;
if(!vis[v]) n++;
vis[u]=vis[v]=true;
add_edge(u,v);add_edge(v,u);
}
for(int i=1;i<=n;i++)
if(!dnf[i]) Tarjan(i,i);
}
int main()
{
while (~scanf("%d",&m)&&m)
{
init();
Input();
solve();
}
return 0;
}
边 双 : 处 理 重 边 : 板 子 边双:处理重边:板子 边双:处理重边:板子
#include <set>
#include <map>
#include <list>
#include <cmath>
#include <stack>
#include <queue>
#include <string>
#include <bitset>
#include <vector>
#include<cstring>
#include <stdio.h>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
#define INF 0x3f3f3f3f
typedef unsigned long long ull;
inline int read(){int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();return s*w;}
const int maxn = 2e5+5;
int head[maxn],tot;
int dfn[maxn],low[maxn],Time;
bool bri[maxn*10];
int col[maxn],cnt;
int n,m;
struct E{
int to,next;
}e[maxn*10];
int cut;
int dp[maxn];
void add_e(int u,int v){
e[tot]={v,head[u]};
head[u]=tot++;
}
void init(){
tot=0;
Time=cnt=0; cut=0;
memset(bri,false,sizeof(bri));
for(int i=0;i<maxn;i++)
head[i]=-1,dfn[i]=low[i]=col[i]=0,dp[i]=0;
}
void Tarjan(int x,int fa){
dfn[x]=low[x]=++Time;
for(int i=head[x];i!=-1;i=e[i].next){
int y=e[i].to;
// if(y==fa) continue;
if(!dfn[y]){
Tarjan(y,i),low[x]=min(low[x],low[y]);
if(low[y]>dfn[x]) bri[i]=bri[i^1]=true,cut++;
}
else if(i!=(fa^1)) low[x]=min(low[x],dfn[y]);
}
}
void dfs(int x){
col[x]=cnt;
for(int i=head[x];i!=-1;i=e[i].next){
int y=e[i].to;
if(col[y]||bri[i]) continue;
dfs(y);
}
}
int U[maxn*10],V[maxn*10];
void Dfs(int x,int fa){
for(int i=head[x];i!=-1;i=e[i].next){
int y=e[i].to;
if(y==fa) continue;
dp[y]=dp[x]+1;
Dfs(y,x);
}
}
int main()
{
while (~scanf("%d%d",&n,&m)&&n&&m){
init();
for(int i=1;i<=m;i++){
int u=read(),v=read();
U[i]=u,V[i]=v;
add_e(u,v),add_e(v,u);
}
for(int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i,i);
// printf("%d\n",cut);
for(int i=1;i<=n;i++) if(!col[i]) ++cnt,dfs(i);
tot=0;
memset(head,-1,sizeof(head));
for(int i=1;i<=m;i++){
int u=col[U[i]],v=col[V[i]];
if(u==v) continue;
add_e(u,v),add_e(v,u);
}
Dfs(1,0);
int Max=-1,pos=0;
for(int i=1;i<=cnt;i++)
if(Max<dp[i]) Max=dp[i],pos=i;
memset(dp,0,sizeof(dp));
Dfs(pos,0);
Max=-1;
for(int i=1;i<=cnt;i++)
Max=max(Max,dp[i]);
int ans=cut-Max;
printf("%d\n",ans);
}
return 0;
}
/*
3 3
1 2
2 1
2 3
*/