【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; } }