给一个无权图,和所有点的一棵生成树,求一个最小割集且只包含一个生成树上的边
据说这个题数据比较弱,判判叶子就行了
找了好几篇博客,才找到了比较像样的正解(解释的思路很对,但代码实现很错。)
因为树上的边删掉肯定可以把点分成两个部分,重要的是求这两部分的点在在原图上的连边个数
枚举树上每条边复杂度可以,但如何快速求原图上的连边是个问题
我们对每个点记录一个v值,对原图上的边,两个端点的v值分别加1,这两个点在树上的lca减2,那么这样对于每一条树上的边L,分成两部分的点之间在原图上的连边数其实也就是L下面所有点的v值的和,对树进行一个后序遍历即可,
#include<bits/stdc++.h>
#define eps 1e-9
#define PI 3.141592653589793
#define bs 1000000007
#define bsize 256
#define MEM(a) memset(a,0,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn=20005,maxm=200005;
int dep[maxn],up[maxn][20];
int fa[maxn];
int du[maxn];
vector<int>w[maxn];
int n;
int init()
{
memset(up,0,sizeof(up));
for(int i=1;i<=n;i++)
up[i][0]=fa[i];
for(int j=1;j<=16;j++)
{
for(int i=1;i<=n;i++)
{
up[i][j]=up[up[i][j-1]][j-1];
}
}
}
int lca(int lx,int rx)
{
if(dep[lx]<dep[rx])
swap(lx,rx);
int a=rx,b=lx;
int c=dep[b]-dep[a];
for(int i=0;i<=16;i++)
{
if(c&(1<<i))
b=up[b][i];
}
if(a==b)
return a;
for(int i=16;i>=0;i--)
{
if(up[a][i]!=up[b][i])
{
a=up[a][i];
b=up[b][i];
}
}
return fa[a];
}
int dfs1(int x,int f)
{
for(int i=0;i<w[x].size();i++)
{
if(w[x][i]==f)
continue;
dep[w[x][i]]=dep[x]+1;
fa[w[x][i]]=x;
dfs1(w[x][i],x);
}
}
int ans;
int dfs(int x,int fa)
{
int now=du[x];
for(int i=0;i<w[x].size();i++)
{
if(fa==w[x][i])
continue;
now+=dfs(w[x][i],x);
}
if(x!=1)
ans=min(ans,now);
return now;
}
int main()
{
int m,T,tca=1,u,v;
cin>>T;
while(T--)
{
memset(du,0,sizeof(du));
scanf("%d %d",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%d %d",&u,&v);
w[u].push_back(v);
w[v].push_back(u);
}
dep[1]=0;
fa[1]=0;
dfs1(1,1);
init();
for(int i=n;i<=m;i++)
{
scanf("%d %d",&u,&v);
int z=lca(u,v);
du[u]++;
du[v]++;
du[z]-=2;
}
ans=0x3f3f3f3f3f;
dfs(1,1);
printf("Case #%d: %d\n",tca++,ans+1);
for(int i=0;i<=n;i++)
w[i].clear();
}
}