其他分享
首页 > 其他分享> > 【tarjan】矿场搭建

【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;
int nums,cuts,cnt,cut[MAX];
int group,vis[MAX];
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++;//普通点个数+1
    for(int i=0;i<int(G[s].size());i++)
    {
        int t=G[s][i];
        if(cut[t] and vis[t]!=group)//点t是割点且在该轮搜索中未曾访问
        {
            cuts++;//割点数量+1
            vis[t]=group;//打上标记

} if(!vis[t])//如果点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])//如果点i未曾访问过且不是割点 { group++; nums=cuts=0; DFS(i); if(cuts==0)//访问结束,若没有连接割点 { if(nums!=1) sum*=(nums-1)*nums/2,ans+=2;
            //如果普通点的个数大于等于2,根据乘法原理计算方案数sum,救援出口数+1 else ans++;
            //否则救援出口数+1,这种情况对方案数没有贡献 } if(cuts==1)//如果只连接了一个割点 { ans++; sum*=nums; } if(cuts==2)//连接了多个割点,没有贡献,不做处理 { } } } cout<<"Case "<<num++<<": "<<ans<<" "<<sum<<endl; } }

 

标签:tarjan,矿场,连通,标记,int,MAX,割点,搭建,分量
来源: https://www.cnblogs.com/dyctj2022/p/16440242.html