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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值