hdu 4674 边双连通缩点+倍增lca+麻烦的讨论 (2013多校联合)

题意不用我说了吧,注意题目中说的图是没有环中套环的。

这题其实不难,就是烦了点,很容易少考虑情况。

我这里是a走到c要经过b的讨论:


1.考虑有相同点的情况

2.考虑3个点在环内(必定Yes)

3.考虑2个点在环内

    a,b在环内:如果a是割点就是No,否则Yes

    (如何判割点,在缩点后的图的边中添加一个信息,然后用LCA就可以了,具体还要细分2种情况)。

    b,c在环内,类似a, b

    a,c在环内, 就是情况2

4.考虑任意两点都不在同一环内的点

   这里情况很多,我们考虑Yes的情况。

   一条链的有两种

   类似二叉树的有两种

   最后一种,很容易忘记:

    b在一个环内,a到c的路径经过点b的联通分量,这条路径进出b的连通分量的只有一个点(设其为点x), 假如    点b是点x,那么Yes,否则No。


代码有很详细的注释

#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int maxn = 100001;
#define pii pair<int, int>
#define mp make_pair
#define pb push_back
struct Edge {
    int v, next;
    bool vis;
} edge[150001 << 1];
int head[maxn];
int E;
int x, y, n, m;
int Btype, Time;
int dfn[maxn], low[maxn], Belong[maxn];
int st[maxn], Top;
const int POW = 20;
int d[maxn], p[maxn][20];
int Log[maxn];
vector <pii > edges[maxn];
int a, b, c, aa, bb, cc, ab, ac, bc;
void add_edge(int s, int t) {
    edge[E].v = t;
    edge[E].vis = 0;
    edge[E].next = head[s];
    head[s] = E++;
}
int get_val() {
    int ret = 0;
    char c;
    while((c=getchar())==' '||c=='\n');
        ret=c-'0';
    while((c=getchar())!=' '&&c!='\n')
            ret=ret*10+c-'0';
    return ret;
}

void dfs(int s) {
    int i, t;
    st[++Top] = s;
    dfn[s] = low[s] = ++Time;
    for (i = head[s]; i != -1; i = edge[i].next) {
        if (edge[i].vis)
            continue;
        edge[i].vis = edge[i ^ 1].vis = 1;
        t = edge[i].v;
        if (!dfn[t]) {
            dfs(t);
            low[s] = min(low[s], low[t]);
        } else
            low[s] = min(low[s], dfn[t]);
    }
    if (dfn[s] == low[s]) {
        Btype++;
        do {
            t = st[Top--];
            Belong[t] = Btype;
        } while (t != s);
    }
}
void BCC(int n) {
    int i;
    Time = 0;
    Btype = 0;
    Top = 0;
    memset(dfn, 0, sizeof(int)*(n+1));
    for (i = 1; i <= n; i++)
        if (!dfn[i])
            dfs(i);
}

void dfs(int u, int fa) {
    d[u] = d[fa] + 1;
    p[u][0] = fa;
    for (int i = 1; i < POW; i++)
        p[u][i] = p[p[u][i - 1]][i - 1];
    for (int i = 0; i < (int)edges[u].size(); i++) {
        if (edges[u][i].first == fa) continue;
        dfs(edges[u][i].first, u);
    }
}

int lca(int a, int b) {
    if (d[a] > d[b])
        a ^= b, b ^= a, a ^= b;
    if (d[a] < d[b]) {
        int del = d[b] - d[a];
        for (int i = 0; i < POW; i++)
                if(del & (1<<i))b = p[b][i];
    }
    if (a != b) {
        for (int i = POW - 1; i >= 0; i--)
            if (p[a][i] != p[b][i])
                a = p[a][i], b = p[b][i];
        a = p[a][0], b = p[b][0];
    }
    return a;
}
inline int find(int a, int k) {  //需找第k个父亲
	for(int i = 0; i < POW; i++)
        if(k&(1<<i))a = p[a][i];
    return a;
}

inline bool inside(int u, pii x) {
     for(int i = 0; i < (int)edges[u].size(); i++)
         if(edges[u][i] == x) return 1;
     return 0;
}
int main() {
    int i, j;
    while (~scanf("%d%d", &n, &m)) {
        E = 0;
        memset(head, -1, sizeof(int)*(n+1));
        while (m--) {
            x = get_val(); y = get_val();
            add_edge(x, y);
            add_edge(y, x);
        }
        BCC(n);//边双连通

        for (i = 0; i <= n; i++)
            edges[i].clear();
        //用edges建缩点后的图
        for (i = 1; i <= n; i++)
            for (j = head[i]; ~j; j = edge[j].next) {
                int v = edge[j].v;
                if (Belong[i] != Belong[v])
                    edges[Belong[i]].pb(mp(Belong[v], v));
            }

        dfs(1, 0);
        scanf("%d", &m);
        while (m--) {
            a = get_val(); c = get_val(); b = get_val();

            //1.相同点
            if (a == c) {
                if (a == b)
                    puts("Yes");
                else
                    puts("No");
                continue;
            }
            if (a == b || c == b) {
                puts("Yes");
                continue;
            }

            int aa = Belong[a], bb = Belong[b], cc = Belong[c];  // 缩点后的联通分量编号

            //2.三点在同一环内
            if(aa == bb && bb == cc) {
                puts("Yes");
                continue;
            }

            int ab = lca(aa, bb), ac = lca(aa, cc), bc = lca(bb, cc);
            //3.两点在同一环内
            if (aa == bb) {     //   a,b在环内
                if (ac == aa) {			//cc是联通分量bb的儿子一侧
                    int t = find(cc, d[cc] - d[aa] - 1);	//t为联通分量cc的第一个儿子
                    if (inside(t, mp(aa, a)))
                        puts("No");
                    else
                        puts("Yes");
                    continue;
                } else {			//cc是在联通分量bb的另一侧
                    if (inside(p[aa][0],mp(aa, a)))  //p[aa][0] 为联通分量cc的第一个父亲
                        puts("No");
                    else
                        puts("Yes");
                    continue;
                }
            }
            if (bb == cc) {   //b,c在环内 类似    a,b在环内
                if (ac == cc) {
                    int t = find(aa, d[aa] - d[cc] - 1);
                    if (inside(t, mp(cc, c)))
                        puts("No");
                    else
                        puts("Yes");
                    continue;
                } else {
                    if (inside(p[cc][0],mp(cc, c)))
                        puts("No");
                    else
                        puts("Yes");
                    continue;
                }

            }

            //考虑博客中说的最后一种,最会忘记的情况
            //找到连通分量bb的最近的两个点x,y
            if (ab == bb)
                x = find(aa, d[aa] - d[bb] - 1);
            else
                x = p[bb][0];
            if (bc == bb)
                y = find(cc, d[cc] - d[bb] - 1);
            else
                y = p[bb][0];
            bool flag = 0;
            int pos;
            for (i = 0; i < (int)edges[x].size(); i++) {
                if(inside(y, edges[x][i])) {
                    flag = 1;  //有相同的交点,那么连通分量一定只有一个出口
                    pos = edges[x][i].second;   //记录出口
                    break;
                }
            }
            if (flag && pos != b) {    //出口为b Yes 否则No
                puts("No");
                continue;
            }
            //两种链的情况
            if (ab == aa && bc == bb && ac == aa) { // a...b...c
                puts("Yes");
                continue;
            }
            if (bc == cc && ab == bb && ac == cc) { // c...b...a
                puts("Yes");
                continue;
            }
            //类似二叉树的两种情况
            if (ab == bb && ac == bc) {
                puts("Yes");
                continue;
            }
            if (ac == ab && bc == bb) {
                puts("Yes");
                continue;
            }
            puts("No");
        }

    }
    return 0;
}


你可能感兴趣的:(LCA)