bzoj 2730: [HNOI2012]矿场搭建(tarjan求点双连通分量)

题目描述

传送门

题解

对无向图求边双连通分量,缩点后会形成一棵树。
但是这道题是删点不是删边,所以求的是点双连通分量,因为一个割点可能属于好几个点双连通分量,所以我们需要对每个点双连通分量新建节点,然后把属于这个点双连通分量的点连接上去。最后也可以形成一棵树,每两个原图中的节点中间一定连着一个点双的节点。
那么这道题方案怎么求呢?最小个数就是子树中不再含有点双节点的点双节点(或者说是点双中的叶子节点)。方案数就是每个对答案有贡献的点双的size(不包括割点)
有种情况需要特判,就是所有的节点缩成了一个点双,那么答案就是C(n,2)

代码

#include
#include
#include
#include
#include
#define N 100000
#define LL long long 
using namespace std;
int point[N],nxt[N],v[N],dfsn[N],low[N],n,m,tot,sz,top,st[N],belong[N],root;
int size[N],ins[N],cnt,x[N],y[N],mark[503][503],belong1[N],ct[N],vis[N],du[N];
LL C[503][503],ans;
int num;
struct data{
    int point[N],nxt[N],v[N],tot;
    void init(){
        tot=0;
        memset(point,0,sizeof(point));
    }
    void add(int x,int y){
        tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
        tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
    }
    void dfs(int x,int fa)
    {
        if (x>n) ct[x]=1;
        else ct[x]=0;
        for (int i=point[x];i;i=nxt[i]){
            if (v[i]==fa) continue;
            dfs(v[i],x);
            ct[x]+=ct[v[i]];
            size[x]++;
        }
    }
    void solve()
    {
        dfs(root,0); num=0; ans=1;
        for (int i=n+1;i<=n+cnt;i++)
         if (ct[i]==1) num++,ans=(LL)ans*size[i];
    }
}E;
void add(int x,int y)
{
    tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
    tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}

void tarjan(int x)
{
    dfsn[x]=low[x]=++sz; ins[x]=1; st[++top]=x;
    for (int i=point[x];i;i=nxt[i]){
        if (!dfsn[v[i]]) {
            tarjan(v[i]);
            low[x]=min(low[v[i]],low[x]);
            if (dfsn[x]<=low[v[i]]) {
                ++cnt; int j;
                while (1){
                    j=st[top--];
                    E.add(cnt+n,j); du[j]++;
                    belong[j]=cnt; 
                    if (j==v[i]) break;
                }
                E.add(x,cnt+n); du[x]++; belong[x]=cnt; 
            }
        }
        else if (ins[v[i]]) low[x]=min(low[x],dfsn[v[i]]);
    }
}
void clear()
{
    E.init();
    memset(du,0,sizeof(du));
    memset(size,0,sizeof(size));
    memset(ct,0,sizeof(ct));
    memset(mark,0,sizeof(mark));
    memset(low,0,sizeof(low));
    memset(dfsn,0,sizeof(dfsn));
    memset(belong,0,sizeof(belong));
    memset(ins,0,sizeof(ins)); sz=cnt=top=0;
}
int main()
{
    freopen("bzoj_2730.in","r",stdin);
    freopen("bzoj_2730.out","w",stdout);
    int T=0; 
    for (int i=0;i<=500;i++) C[i][0]=1;
    for (int i=1;i<=500;i++)
     for (int j=1;j<=i;j++) C[i][j]=C[i-1][j-1]+C[i-1][j];
    while (true){
        scanf("%d",&m); tot=0; n=0; T++;
        if (!m) break; 
        memset(point,0,sizeof(point));
        for (int i=1;i<=m;i++) {
            scanf("%d%d",&x[i],&y[i]);
            add(x[i],y[i]); n=max(x[i],n);
            n=max(y[i],n);
        }
        clear(); 
        for (int i=1;i<=n;i++) 
         if (!dfsn[i]) tarjan(i);
        root=1;
        for (int i=1;i<=n;i++)
         if (du[i]>=2) root=i;
        E.solve();
        if (cnt==1) {
            num=2;
            ans=C[n][2];
        }
        printf("Case %d: %d %lld\n",T,num,ans);
    }
}

你可能感兴趣的:(tarjan,算法)