题目链接:
http://poj.org/problem?id=3417
题目大意:
首先给定一棵有n个点的树,然后在这棵树上添加m条边。
求去掉原树上的一条边和添加的一条边之后,能使得这棵树分裂的放法数。
解题思路:
主要思路是来自网上的大神,有事不懂请问度娘。
基本思想:
添加一条边之后,必定形成一个环。
如果原树上的某一条边没有被环覆盖,那么这样的边是很脆弱的,我们去掉这条边之后,树必然分裂成两半,方法数为m。
如果原树上的某一条边仅被环覆盖过一次,那么去掉这样的边,以及产生该环的新边,树也将会分裂成两半,方法数为1。
如果原树上的某一条边被覆盖两次或者是两次以上,那么无论是不是去掉这条边,树都不会分裂成两半,所以方法数为0.
所以我们现在只要统计出来每条边被环所覆盖的次数,然后进行一次扫描即可得出最终解。
实现方法:
定义一个dp数组,dp[u]表示u的父边被覆盖的次数,父边指的是u与其父亲节点连接的边。
添加一条新边(u,v)之后,必定会形成一个环,并且这个环一定是这样的u--->lca(u,v)--->v--->u。
那么dp[u]和dp[v]肯定都会+1,但是dp[lca]的值是不发生改变的,因为lac的父边并没有被覆盖。
但是我们这树形dp的过程中会使得dp[lca]+=2,显然这样是不对的。
所以我们在做树形dp之前就应该做如下操作:dp[u]++,dp[v]++,dp[lca]-=2。
转移方程:dp[u]+=dp[v]。(v是u的孩子节点)
源代码:
#include<stdio.h>
#include<iostream>
#include<math.h>
#include<string.h>
#include<string>
#include<map>
#include<vector>
#include<set>
#include<stack>
#include<queue>
#include<algorithm>
using namespace std;
#define INF 030f0f0f0f
#define eps 1e-8
typedef long long LL;
const int N=100005;
int first[N*2],next[N*2],to[N*2],edgecnt; //记录树图
int _first[N*2],_next[N*2],_to[N*2],_edgecnt; //记录询问
int res[N][3]; //记录每次询问长生的lca
int pre[N*2]; //记录每个点的祖先
int vis[N*2]; //对访问过的点进行标记
int dp[N*2]; //每个点的父边被环所覆盖的次数
int sum,n,m; //最后求得的总和,点的个数,新边的个数
int Find(int x)
{
if(x!=pre[x])
pre[x]=Find(pre[x]);
return pre[x];
}
void add1(int a,int b) //按照边建立原来的图
{
to[edgecnt]=b;
next[edgecnt]=first[a];
first[a]=edgecnt++;
return;
}
void add2(int a,int b) //记录下询问的点,进行离线处理
{
_to[_edgecnt]=b;
_next[_edgecnt]=_first[a];
_first[a]=_edgecnt++;
return;
}
void tarjian(int now) //用tarjian算法求取最近公共祖先
{
int i,j,k,t,kid;
vis[now]=1;
pre[now]=now;
for(i=_first[now];i!=-1;i=_next[i])
{
kid=_to[i];
if(vis[kid])
res[i/2][2]=Find(kid);
}
for(i=first[now];i!=-1;i=next[i])
{
kid=to[i];
if(!vis[kid])
{
tarjian(kid);
pre[kid]=now;
}
}
return;
}
void DP(int now) //求取每条父边被覆盖的次数
{
int i,kid;
vis[now]=1;
for(i=first[now];i!=-1;i=next[i])
{
kid=to[i];
if(vis[kid]) continue;
DP(kid);
dp[now]+=dp[kid];
}
if(dp[now]==1) sum++;
if(dp[now]==0 && now!=1) sum+=m;
return;
}
int main()
{
freopen("in.txt","r",stdin);
int i,j,a,b,c,d,e,f;
while(scanf("%d%d",&n,&m)==2)
{
edgecnt=0;
memset(first,-1,sizeof(first));
for(i=1;i<n;i++)
{
scanf("%d%d",&a,&b);
add1(a,b);
add1(b,a);
}
_edgecnt=0;
memset(_first,-1,sizeof(_first));
for(i=0;i<m;i++)
{
scanf("%d%d",&a,&b);
add2(a,b);
add2(b,a);
res[i][0]=a,res[i][1]=b;
}
memset(vis,0,sizeof(vis));
tarjian(1);
memset(dp,0,sizeof(dp));
for(i=0;i<m;i++) //在做树形dp之前对dp数组进行操作,保证结果的正确性
{
dp[res[i][0]]++;
dp[res[i][1]]++;
dp[res[i][2]]-=2;
}
memset(vis,0,sizeof(vis));
sum=0;
DP(1);
printf("%d\n",sum);
}
return 0;
}