題目鏈接:
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;
}