tarjan -LCA POJ-3417-Network


先放一下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;

}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章