[ BZOJ1123 ] BLO(tarjan点双连通分量)

题干:

  Byteotia城市有n个 towns m条双向roads. 每条 road 连接 两个不同的 towns ,没有重复的road. 所有towns连通。输出n个数,代表如果把第i个点去掉,将有多少对点不能互通。

题解:

  本题要求求去掉第 i 个点后消失了多少对节点。(关于加减节点的问题就容易想到点双连通分量

  拿到这道题,作者首先想到的就是每去掉一个点,就会少(n-1)对点。

  后来看样例发现:好像这个节点对是有顺序的,那就是 2*(n-1)对点。

  作者后来转念一想,这不是 tarjan 专题吗?不可能出个这么水的题。。。

  于是又手模了几个样例,才发现刚才想的也对:(但太片面了)  

  1、若现在这个节点为 x ,且它并不是割点,那么它只能是环里的一个不是割点的点,答案就是2*(n-1)

(割点是指强联通分量中的一个特殊的点,不在环中的单个点也为割点)

  2、若现在这个节点 x 是割点,那么它的消失一定会构造出森林,那么消失的节点对也就不止2*(n-1)对 。答案应该是每一棵树的大小乘上原图上其余部分的大小,最后再乘2。(不同顺序也算)

ans+=2*siz[to]*(n-siz[to])*1ll;   (错解)

  于是作者便高高兴兴地开始打代码 —— 0分。。。

  为什么呢?  

  上面那个式子将我们枚举的那个断点算重了好多次。不可以乘二。。。

  我们尝试换一种思路:

ans+=siz[to]*(n-siz[to])*1ll;

  (虽然就少了'*2' ,但至少不会算重啊。。。)

  其实 tarjan 就是基于 dfs 而衍生出来的算法。dfs有一个特性,它在遍历一张图时,不可遍历重复的点(遍历树时,不可遍历其父节点),否则就会死循环。而这道题,我们需要知道这个节点 x 以上部分的大小,从而将这以上部分也作为一棵子树进行统计答案。而 tarjan(dfs) 却无法访问这棵“子树”,这就造成我们少计算了一种情况。统计答案:

ans+=(sum+1)*(n-sum-1) + (n-1)

  其中的 sum 为真正的子树和(其实特别好维护,就是dfs求),我用(n-sum)就间接地求出了这个节点上面的“子树”。为什么sum要减一加一呢?我们再回过头去看,在统计每一个真正的子树所做的贡献时,我们将枚举的断点归到了其余的部分,而不是这棵子树,所以这种情况也需要同样处理。

  那 ‘ +(n-1) ’是怎么回事呢?我们再看一下这个算法,所有的子树都考虑了节点对 顺序的情况(每两个子树或直接或间接地乘了两遍),但相对我们枚举的切点,只乘了一遍,所以要补救一下。

  最后一步就是求割点了。(板子)

1     opt=0;
2     if(dfn[to]>=low[x]){
3         tarjan(to);
4         low[x]=min(low[x],low[to]);
5         if(low[x]>=dfn[to]) opt++;
6         if(x!=root||opt>1)  cut[x]=1;
7     }
8     else low[x]=min(low[x],dfn[to]);    

(注意一下,针对有向图的tarjan,需要判断是否入队;而无向图就不用)

Code:

 1 #include<cstdio>
 2 #include<cstring>
 3 #define $ 100010
 4 using namespace std;
 5 int m,n,first[$],tot,dfn[$],low[$],tar,siz[$];
 6 long long ans[$];
 7 bool cut[$];
 8 struct tree{    int to,next;    }a[$*10];
 9 inline int min(int x,int y){    return x<y?x:y;    }
10 inline void add(int x,int y){
11     if(x==y) return;
12     a[++tot]=(tree){    y,first[x]    };
13     first[x]=tot;
14     a[++tot]=(tree){    x,first[y]    };
15     first[y]=tot;
16 }
17 inline void tarjan(int x){
18     low[x]=dfn[x]=++tar; siz[x]=1;
19     int opt=0,sum=0;
20     for(register int i=first[x];i;i=a[i].next){
21         int to=a[i].to;
22         if(!dfn[to]){
23             tarjan(to);
24             siz[x]+=siz[to];
25             low[x]=min(low[x],low[to]);
26             if(low[to]>=dfn[x]){
27                 opt++;
28                 ans[x]+=1ll*siz[to]*(n-siz[to]);
29                 sum+=siz[to];
30             }
31             if(x!=1||opt>1) cut[x]=1;
32         }
33         else low[x]=min(low[x],dfn[to]);
34     }
35     if(cut[x]) ans[x]+=1ll*(n-sum-1)*(sum+1)+1ll*(n-1);
36     else       ans[x]=1ll*2*(n-1);
37 }
38 signed main(){
39     scanf("%d%d",&n,&m);
40     for(register int i=1,x,y;i<=m;++i) scanf("%d%d",&x,&y),add(x,y);
41     tarjan(1);
42     for(register int i=1;i<=n;++i) printf("%lld
",ans[i]);
43 }
View Code
越努力 越幸运
原文地址:https://www.cnblogs.com/OI-zzyy/p/11182990.html