P7687 题解

[CEOI2005] Critical Network Lines

题目大意

给定一个 $N$ 个点 $M$ 条边的无向连通图,其中有 $K$ 个点属于集合 $A$,有 $L$ 个点属于集合 $B$(可能会存在一个点同时属于两个集合的情况)。求图中有多少条边满足去掉这些边中任意一条或多条边时图中存在部分点与至少一个集合中的所有点都不连通,输出满足条件的边的总数并输出这些边的两个端点。其中 $1 \le N \le 10^5$,$1 \le M \le 10^6$,$1 \le K \le N$,$1 \le L \le N$。

思路

题目大意中提到了“去掉任意一条或多条边时图中存在部分点与至少一个集合中的所有点都不连通”,由此可以很容易地联想到可以用 tarjan 求图中的割边。仔细观察可以发现这道题并不能算是裸的割边,还需要保证存在部分点与至少一个集合中的所有点都不连通。

显然去掉一条满足条件的边后,原图会分成若干个互不连通的部分,并且某一部分内部没有点属于集合 $A$ 或没有点属于 $B$。可以在求割边的过程中分别统计一下以每个点为根的子树中有多少个点属于集合 $A$,多少个点属于集合 $B$,然后再分情况进行讨论。

具体来说,对于图中的任意一点,令这一点为 $x$,则令:$$ f(x) = \sum_{i\in son(x)}{[i\in A]} $$ 同理:$$ g(x) = \sum_{i\in son(x)}{[i\in B]} $$ 对于答案统计,具体可以分为两种情况:

  1. 如果 $f(x) = 0$ 或 $g(x) = 0$,那么这条边就是满足条件的,某个集合中的所有点都在子树外,去掉这条边后其子树内所有点都无法到达该集合中任意一点。
  2. 如果 $f(x) = K$ 或 $g(x) = L$,那么这条边也是满足条件的,某个集合中所有点都在该子树内,去掉这条边后其子树外所有点都无法到达该集合中任意一点。

如果属于以上两种情况中的任意一种,那么就更新答案,并把该边的两个端点存储下来。至于每个点的子树大小直接在 tarjan 回溯时顺便统计一下即可。剩下的就是求割边的事了,可以考虑用 tarjan 求割边,总的时间复杂度为 $O(N + M)$,总的空间复杂度为 $O(N + M)$,稳过。

具体细节请看代码……

代码

#include <iostream>
#define MAXN 100005
#define MAXM 1000005
using namespace std;
int n, m, k, l, p, q, tot, sum;
struct edge{int from, to, nxt;}e[MAXM << 1];
int dfn[MAXN], low[MAXN], a[MAXN], b[MAXN], head[MAXN], cnt = 1;
struct node{int p, q;}ans[MAXM];
int read(){
    int t = 1, x = 0;char ch = getchar();
    while(!isdigit(ch)){if(ch == '-')t = -1;ch = getchar();}
    while(isdigit(ch)){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * t;
}
void write(int x){
    if(x < 0){putchar('-');x =-x;}
    if(x >= 10)write(x / 10);
    putchar(x % 10 ^ 48);
}
void add(int u, int v){cnt++;e[cnt].from = u;e[cnt].to = v;e[cnt].nxt = head[u];head[u] = cnt;}
void tarjan(int now, int fat){ // 运用 tarjan 求割边
    tot++;dfn[now] = tot;low[now] = tot;
    for(int i = head[now] ; i != 0 ; i = e[i].nxt){
        int v = e[i].to;
        if(dfn[v] == 0){tarjan(v, i);
            low[now] = min(low[now], low[v]);
            a[now] += a[v];b[now] += b[v]; // 更新 f(v) 和 g(v)
            if(low[v] > dfn[now] && ((a[v] == 0 || a[v] == k) || (b[v] == 0 || b[v] == l))){ // 判定割边并分情况讨论
                sum++;ans[sum].p = now;ans[sum].q = v; // 保存答案
            }
        }else if(i != (fat ^ 1)){low[now] = min(low[now], dfn[v]);}
    }
}
int main(){
    n = read();m = read();k = read();l = read();
    for(int i = 1 ; i <= k ; i ++)a[read()] = 1;
    for(int i = 1 ; i <= l ; i ++)b[read()] = 1;
    for(int i = 1 ; i <= m ; i ++){
        p = read();q = read();add(p, q);add(q, p);
    }tarjan(1, 0);write(sum);putchar('\n');
    for(int i = 1 ; i <= sum ; i ++){
        write(ans[i].p);putchar(' ');write(ans[i].q);putchar('\n');
    }return 0;
} // 完结撒花~~
posted @ 2023-08-22 10:12  tsqtsqtsq  阅读(22)  评论(0)    收藏  举报  来源