poj 3417--LCA+樹形dp

題目鏈接:

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章