【tarjan】矿场搭建

一、题目传送门:P3225 [HNOI2012]矿场搭建 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

二、思路分析:题目抽象后时这样的——给定一图G(V,E),标记其中某些点,使去掉任意一节点后其余所有节点都能到达标记点,求标记节点数和方案数

  1、首先割点是不会被标记的,因为割点如果坍塌,其连接的连通分量将失去逃生机会;如下图,标记割点不可能是最优解。   ·              

   2、考虑每个连通分量与割点的连接情况:

  (1)若该连通分量没有连接割点,即孤立连通分量(如图中的9),那么其内部标记的点数a与连通分量包含的点个数b有关(a=1,b=1;a>=2,b=2);

  (2)若该连通分量只与一个割点相连(如图中的7),那么该分量里必须有一个标记点,因为若没有标记点,割点坍塌后该连通分量里的人无法逃生;

  (3)若该连通分量与两个及以上个割点相连(如图中的1,2,3,8),则该分量里没有标记点,因为无论哪一个割点坍塌,该分量里的人都可以通过其它的割点到另外的连通分量里逃生。

  3、证明了以上性质这个题就解决了,步骤如下

  (1)利用tarjan跑出所有割点;

  (2)通过DFS统计每一个连通分量中的普通节点个数、连接的割点个数;

  (3)根据上文性质计算所需标记点的个数;

  (4)根据乘法原理,计算总方案数(具体式子见代码)

三、solution:

 

#include<bits/stdc++.h>
#define MAX 60000
using namespace std;
typedef long long ll;//记得long long
ll ans,sum=1;
int n,m,root,num=1;
int dfn[MAX],low[MAX],Time=1;//tarjan配套变量
int nums,cuts,cnt,cut[MAX];//非割点数,(连接的)割点数,割点总数,割点标记
int group,vis[MAX];//DFS轮数,已访问标记
vector<int> G[MAX];
void AddG(int from,int to)
{
    G[from].push_back(to);
}
void read()
{
    for(int i=1;i<=m;i++)
    {
        int x,y;
        cin>>x>>y;
        n=max(n,max(x,y));
        AddG(x,y);
        AddG(y,x);
    }
}
void init()//记得初始化
{
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(cut,0,sizeof(cut));
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
    {
        G[i].clear();
    }
    n=0,m=0,root=0,ans=0,cnt=0,sum=1,Time=1;
}
void tarjan(int x)//tarjan跑出所有割点
{
    dfn[x]=low[x]=Time++;
    if(x==root and !G[x].size())
    {
        return;
    }
    int flag=0;
    for(int i=0;i<int(G[x].size());i++)
    {
        int y=G[x][i];
        if(!dfn[y])
        {
            tarjan(y);
            low[x]=min(low[x],low[y]);
            if(low[y]>=dfn[x])
            {
                flag++;
                if(x!=root or flag>1) cut[x]=1;    
                cnt++;
            }
        }
        low[x]=min(low[x],dfn[y]);
    }
}
void DFS(int s)//通过DFS统计联通分量内共有多少个点及割点
{
    vis[s]=group;
    nums++;
    for(int i=0;i<int(G[s].size());i++)
    {
        int t=G[s][i];
        if(cut[t] and vis[t]!=group)//若该点是割点并且未曾访问
        {
            cuts++;
            vis[t]=group;
        }
        if(!vis[t])
        {
            DFS(t);
        }
    }
}
int main()
{
    while(1)
    {
        init();
        cin>>m;
        if(!m)return 0;
        read();
        for(int i=1;i<=n;i++)
        {
            if(!dfn[i])
            {
                root=i;
                tarjan(i);
            }
        }
        for(int i=1;i<=n;i++)
        {
            if(!vis[i] and !cut[i])
            {
                group++;
                nums=cuts=0;
                DFS(i);
                if(cuts==0)//该连通分量没有连接割点
                {
                    if(nums>1) sum*=(nums-1)*nums/2,ans+=2;
                    else ans+=1;
                }
                if(cuts==1)//连接一个割点
                {
                    ans++;//只放一个
                    sum*=nums; 
                }
                if(cuts==2)//连接两个割点,可以不放
                {
                    
                }
            }
        }
        cout<<"Case "<<num++<<": "<<ans<<" "<<sum<<endl;
    }    
}

 

posted @ 2022-07-03 17:18  正在加载90%  阅读(107)  评论(0编辑  收藏  举报