题意:n个点,m条边的无向图,通过DFS遍历,m条边中前面的n-1条边形成深度优先生成树,后面的自然就是形成环(没有自环)。问从DFS生成树中最少选择多少边,能够使得所有的环都至少有一条边在你选择的集合中。
题解:
考虑DFS生成树,必然没有横叉边(即一个孩子都另一个孩子的边,要不DFS就应该遍历过去),所有的环都是一个结点与它祖先结点的边形成的。
那么这题做法就是进行一次DFS遍历,在回溯的时候,到了now这个结点,那么从他引出的所有后m-n+1条边中,到达深度更深的那些边,也就是如果有now->to这么一条边且dep[now]<dep[to],那么在now结点上面选择边都不会影响到这一条边所形成的环,就意味着必须选择now到to之间的一条边。
由于是回溯时处理,所以需要判断是否now~to之间已经有边被选择了,那么我们考虑如果没有被选择,即现在需要进行选边操作:
有回溯处理操作可以看出,所有深度大于now的出发结点的边所形成的环都已经被处理了,没处理的边就只能是终点在now~to之间,且起点在now之上的边了,任意一种这种边必然会经过now->son,son代表从now到to所经历的第一个结点,那么删除这条边就是使得环减少最多的方案。这种贪心策略之所以正确,是因为在必然要选边的时候选择了能使得环数减少最多的边,选择其他边意味着后面会有一些环还需要选择边。
具体实现的话,就是先对DFS生成树进行树链剖分,生成一个树状数组,某个结点等于1意味着它到它父亲结点的边被选择了,判断now~to是否有边被选择,就是看树状数组相应区间是否有至少一个1,就是和是否大于0.
选择边now~to之间的边就是选择now到to经历的第一条边,就是把now结点的下一个结点在树状数组对应位置+1.
总体复杂度就是:
树链剖分:O(n)
DFS处理:O(n)
查询操作:O (m*logn*logn)
选边操作:O(m*logn*logn)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=3005,M=40005;
int ar[N];
inline int lowbit(int x){
return x&-x;
}
int sum(int i){
int s=0;
for(;i>0;s+=ar[i],i-=lowbit(i));
return s;
}
void add(int i){
for(;i<N;ar[i]+=1,i+=lowbit(i));
}
int check(int l,int r){
if(l>r)return 0;
return sum(r)-sum(l-1);
}
struct Edge{
int to,nxt;
};
struct Graph{
int head[N],nc;
Edge edge[M*2];
void Init(){
memset(head,-1,sizeof(head));
nc=0;
}
void Add(int a,int b){
edge[nc].to=b;edge[nc].nxt=head[a];head[a]=nc++;
}
void AddTwo(int a,int b){
edge[nc].to=b;edge[nc].nxt=head[a];head[a]=nc++;
edge[nc].to=a;edge[nc].nxt=head[b];head[b]=nc++;
}
}T,G;
struct STK_Heavy{//now,fa,e,dep
int now,fa,e,dep;
STK_Heavy(){}
STK_Heavy(int _n,int _f,int _e,int _d){
now=_n,fa=_f,e=_e,dep=_d;
}
}Hstk[N*2];
struct STK_Create{//now,ac,e
int now,ac,e;
STK_Create(){}
STK_Create(int _n,int _a,int _e){
now=_n,ac=_a,e=_e;
}
}Cstk[N*2];
int dep[N],anc[N],pa[N],tid[N],heavy[N],size[N],ID,n,m;
bool mark[N];
void DFS_Heavy(int root){
int top=0;
memset(mark,false,sizeof(mark));
Hstk[top]=STK_Heavy(root,-1,T.head[root],0);
while(top>=0){
STK_Heavy elem=Hstk[top];
if(!mark[elem.now]){
mark[elem.now]=true;
size[elem.now]=1;
heavy[elem.now]=-1;
pa[elem.now]=elem.fa;
dep[elem.now]=elem.dep;
}
if(elem.e==-1){
if(top){
size[elem.fa]+=size[elem.now];
if(heavy[elem.fa]==-1||size[heavy[elem.fa]]<size[elem.now]){
heavy[elem.fa]=elem.now;
}
}
top--;
continue;
}
int to=T.edge[elem.e].to;
Hstk[top].e=T.edge[elem.e].nxt;
if(mark[to])continue;
Hstk[++top]=STK_Heavy(to,elem.now,T.head[to],elem.dep+1);
}
}
void DFS_Create(int root){
int top=0;
Cstk[0]=STK_Create(root,root,T.head[root]);
memset(mark,false,sizeof(mark));
while(top>=0){
STK_Create elem=Cstk[top];
if(!mark[elem.now]){
mark[elem.now]=true;
tid[elem.now]=++ID;
anc[elem.now]=elem.ac;
if(heavy[elem.now]!=-1){
Cstk[++top]=STK_Create(heavy[elem.now],elem.ac,T.head[heavy[elem.now]]);
continue;
}
}
if(elem.e==-1){
top--;
continue;
}
int to=T.edge[elem.e].to;
Cstk[top].e=T.edge[elem.e].nxt;
if(mark[to])continue;
Cstk[++top]=STK_Create(to,to,T.head[to]);
}
}
bool Query(int f,int s){
while(anc[f]!=anc[s]){
int fs=anc[s];
if(check(tid[fs],tid[s])!=0)return true;
s=pa[fs];
}
if(f==s)return false;
else if(check(tid[f]+1,tid[s])!=0)return true;
else return false;
}
void Set(int f,int s){
int p;
while(anc[f]!=anc[s]){
s=pa[p=anc[s]];
}
if(s==f){
add(tid[p]);
}
else{
add(tid[f]+1);
}
}
void Tree_Chain(){
memset(ar,0,sizeof(ar));
ID=0;
DFS_Heavy(1);
DFS_Create(1);
}
int ans;
void dfs(int now){
mark[now]=true;
for(int i=T.head[now];i!=-1;i=T.edge[i].nxt){
int to=T.edge[i].to;
if(mark[to])continue;
dfs(to);
}
for(int i=G.head[now];i!=-1;i=G.edge[i].nxt){
int to=G.edge[i].to;
if(!Query(now,to)){
Set(now,to);
ans++;
}
}
}
int main(){
int a,b;
while(scanf("%d%d",&n,&m)!=EOF){
if(n==0&&m==0)break;
T.Init();
G.Init();
for(int i=1;i<=n-1;i++){
scanf("%d%d",&a,&b);
T.AddTwo(a,b);
}
Tree_Chain();
for(int i=n;i<=m;i++){
scanf("%d%d",&a,&b);
if(dep[a]>dep[b])swap(a,b);
G.Add(a,b);
}
memset(mark,false,sizeof(mark));
ans=0;
dfs(1);
printf("%d\n",ans);
}
return 0;
}