tarjan -LCA POJ-3417-Network

本文介绍使用Tarjan算法离线求最近公共祖先(LCA)的方法,并应用于解决一道涉及树形结构与新增边的问题。通过DFS遍历树结构,实现对新增边的有效处理,最终统计出使得图不连通的方案数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


先放一下tarjan离线求LCA的模板

就是先dfs 然后访问新边的时候,如果碰到标记过的点,那么就说明被dfs过了

两个点找lca


void addedge2(int x,int y)
{
    pra[e].to = y;
    pra[e].next = head2[x];
    head2[x] = e++;
}

int f[SIZE_D];
int findc(int x)
{
    if (f[x] == x) return x;
    return f[x] = findc(f[x]);
}

int flag[SIZE_D],dp[SIZE_D];
void tarjan(int ver,int fa)
{
    f[ver] = ver;
    for (int i = head[ver]; i != -1; i = pra[i].next){
        int u = pra[i].to;
        if (u != fa){
            tarjan(u,ver);
            f[u] = ver;
        }
    }
    flag[ver] = 1;
    ///标记flag为何要放在两个图遍历的中间呢?能不能放后面?能不能放前面?
    for (int i = head2[ver]; i != -1; i = pra[i].next){
        int u = pra[i].to;
        if (flag[u] != 0){
            int t = findc(u);
            dp[u]++;dp[ver]++;dp[t]-= 2;
        }
    }
}

啊呀呀……说一下这道题题意

题意:有一棵树,有n个点,现在再增加m条新边,问你去掉原来老边,去掉一条新边,使得图不连通,有几种方案数?

做法:一条边,假如存在在一个环中,那就去掉它,再去掉环中的另一条边就可以了。假如不存在在一个环中,那它就是一个桥(去掉这条边图不连通)。假如存在两个或者两个以上的环中,那么去掉这条边,无论再去掉哪条边,都不会使图不连通。

用点覆盖度表示当前点的上一条边在几个环中。

做法就是每次加进来一条新的边,那就找这两个节点的LCA ,d【v】++;d【u】++;d【lca】-=2;

然后dfs 每个点的dp值就是孩子节点(不包括孙子)的dp值之和


poj交题碰到的奇葩事:

1.老是502,504 我已经不爱poj了TAT

2.数组开小了居然返回的是TLE

3.没有把数组全部初始化只把数组部分初始化 居然返回的是RE


放代码

/*
给出原有边之后就先预处理所有边的父亲
每加一条边,加到u和v上就把u和v直到LCA的祖先节点都加1.然后祖先节点先加2再减2
从第一个到最后一个算每个节点的覆盖度
若覆盖度是0 那么就ans+= 新加的边
若覆盖度是1 那么就是删去这条边 ans+= 1
关于新加边存在x=y的情况,没判掉wa
图论三大坑自环重边点序号
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define SIZE_D 200005
#define SIZE_B 400005
using namespace std;

int N,M;
struct pp
{
    int to,next;
}pra[SIZE_B];
int e,head[SIZE_D];
int head2[SIZE_D];
void init()
{
    e = 0;
    memset(head, -1, sizeof(head));
    memset(head2, -1, sizeof(head2));
}
void addedge(int x,int y)
{
    pra[e].to = y;
    pra[e].next = head[x];
    head[x] = e++;
}
void addedge2(int x,int y)
{
    pra[e].to = y;
    pra[e].next = head2[x];
    head2[x] = e++;
}

int f[SIZE_D];
int findc(int x)
{
    if (f[x] == x) return x;
    return f[x] = findc(f[x]);
}

int flag[SIZE_D],dp[SIZE_D];
void tarjan(int ver,int fa)
{
    f[ver] = ver;
    for (int i = head[ver]; i != -1; i = pra[i].next){
        int u = pra[i].to;
        if (u != fa){
            tarjan(u,ver);
            f[u] = ver;
        }
    }
    flag[ver] = 1;
    ///标记flag为何要放在两个图遍历的中间呢?能不能放后面?能不能放前面?
    for (int i = head2[ver]; i != -1; i = pra[i].next){
        int u = pra[i].to;
        if (flag[u] != 0){
            int t = findc(u);
            dp[u]++;dp[ver]++;dp[t]-= 2;
        }
    }
}
void getdp(int ver,int fa)
{
    for (int i = head[ver]; i != -1; i = pra[i].next){
        int u = pra[i].to;
        if (u != fa){
        getdp(u,ver);
        dp[ver]+=dp[u];
        }
    }
}
bool kn[SIZE_D];
int main()
{
    //freopen("input.txt","r",stdin);
    while (~scanf("%d %d",&N,&M)){
        init();
        memset(kn,0,sizeof (kn));//增加了kn数组看看是从哪个下标开始的
        for (int i = 1; i < N; i++){
            int tempx,tempy;
            scanf("%d %d",&tempx,&tempy);
            kn[tempx] = kn[tempy] = 1;
            addedge(tempx,tempy);
            addedge(tempy,tempx);
        }
        for (int i = 0; i < M; i++){
            int tempx,tempy;
            scanf("%d %d",&tempx,&tempy);
            kn[tempx] = kn[tempy] = 1;
            addedge2(tempx,tempy);
            addedge2(tempy,tempx);
        }

        int ispran = 0;
        memset(flag,0,sizeof(flag));
        memset(dp,0,sizeof(dp));
        int root;
        for (int i = 0; i < SIZE_D-5; i++){
            if (kn[i] == 1){

        tarjan(i,-1);
        getdp(i,-1);
                root = i;
                break;
            }
        }
        int sum = 0;
        int res = 0;
        for (int i = 0; i < SIZE_D-5; i++){
            if (kn[i] == 1 && i != root){
                if (dp[i] == 1)res++;
                if (dp[i] == 0) res +=M;
                sum++;
                if (sum == N) break;
            }

        }
        printf("%d\n",res);
    }

    return 0;

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值