【HNOI2012】矿场搭建

题面

https://www.luogu.org/problem/P3225

题解

首先先对割点这个东西有一个形象的理解。

一个割点就是两个或多个点双的交,所以不能把它理解成“缩点后形成的树”上的边。

也可以理解成是一个点双的边界且不能是图的边界,所以我认为割点并不是很优美。

两个点双之间,最多只有一个割点(要不然就不叫割点了)

割点和桥的对比:

点双要求严格,所以点双都较小,所以割点好找。($low[y]>=dfn[x]$)(说一句,在此过程中,父边能不能更新$low[x]$是不影响结果的)

边双要求不严格,所以边双比较大,所以割边难找。($low[y]>dfn[x]$)(此过程中,显然不能更新)

再说割点的求法(多年不写我早已遗忘)

当$low[y]>=dfn[x]$时,应该弹栈,直到弹到$y$,再把$x$“算到”这个点双里面(如果弹栈到$x$,先搜的下面的已经判定和$x$形成一个点双的部分,就被划归为这个点双里了,但其实,这些部分应该和$x$和$x$上面的点是一个点双里的)

这样求点双是没有要特判的部分的。

但是求割点的时候,由于不能是边界上的点,所以当$x$是根时,要多维护一个$rs$,记录$x$的不相交的儿子总共有多少个,如果$rs<=1$,就出现了乌龙,$x$不是割点,因为$x$没有上面的部分。

回到我们这题

我们把原图中的一个点双建一个点,一个割点建一个点,一个割点向它所在的点双们连边,这样就形成了一颗树,并且割点所代表的点,他们的度数不为$1$,如果看做无根树 ,他们一定不在边界上,在边界上的点一定是点双代表的点。

我们只考虑在非割点上建出口的情况,对于度为$1$的代表点双的点,我们必须在其中任意一个非割点建一个出口。

对于度$>=2$的代表点双的点,我们可以通过它走到其他叶子点上,所以不用建。

还有一种情况是特殊考虑的,就是整个联通块缩成一个孤立的点,也就是没有割点,那我们任选两个建出口(一个塌了走另外一个)

那如果考虑,可以在割点上建出口,答案会不会更优呢?

本来度数就$>=2$的点不需要建,我们不考虑。

叶子点双是必须建的,如果我们在割点上建,可以一个当多个使用啊(滑稽),但其实这样是错误的。因为如果一个叶子点双,只在割点上建,那么割点塌了就没的逃了。。。。。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<stack>
#define LL long long 
#define N 550
#define ri register int
using namespace std;

int dfn[N],low[N],cut[N];
int n,num,ctc,cc,T;
vector<int> to[N],c1[N],c2[N];
stack<int> s;
LL ans1,ans2;

void add_edge(int u,int v) {
  to[u].push_back(v); to[v].push_back(u);
}

void tarjan(int x,int ff) {
  dfn[x]=low[x]=++cc;
  s.push(x);
  
  int rs=0;
  for (ri i=0;i<to[x].size();i++) {
    int y=to[x][i];
    if (!dfn[y]) {
      tarjan(y,x);
      low[x]=min(low[x],low[y]);
      if (low[y]>=dfn[x]) {
        rs++;
        cut[x]=1;
        int t;
        num=ctc=0;
        while (1) {
          t=s.top(); s.pop();
          if (cut[t]) ++ctc; else ++num;
          if (t==y) break;
        }
        if (cut[x]) ++ctc; else ++num;
        c1[x].push_back(ctc); c2[x].push_back(num);
      }
    }
    else low[x]=min(low[x],dfn[y]);
  }
  
  if (x==ff && rs<=1) {
    for (ri i=0;i<c1[x].size();i++) c1[x][i]--,c2[x][i]++;
  }
  
  for (ri i=0;i<c1[x].size();i++) {
    if (c1[x][i]==0) {
      ans1+=2;
      ans2*=((c2[x][i]-1)*c2[x][i])/2;
    }
    else if (c1[x][i]==1) {
      ans1++;
      ans2*=c2[x][i];
    }
  }
}

int main() {
  freopen("mine1.in","r",stdin);
  int m,u,v;
  while (scanf("%d",&m)==1 && m) {
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(cut,0,sizeof(cut));
    for (ri i=1;i<=n;i++) to[i].clear(),c1[i].clear(),c2[i].clear();
    while (!s.empty()) s.pop();
    n=0; cc=0;
    ans1=0; ans2=1;
    for (ri i=1;i<=m;i++) {
      scanf("%d %d",&u,&v);
      add_edge(u,v);
      n=max(n,u);
      n=max(n,v);
    }
    for (ri i=1;i<=n;i++) if (!dfn[i]) tarjan(i,i);
    printf("Case %d: %lld %lld\n",++T,ans1,ans2);
  }
  return 0;
}

猜你喜欢

转载自www.cnblogs.com/shxnb666/p/11344735.html