Luogu P3225 [HNOI2012]矿场搭建


思路
(这个题当时去qbxt的时候hzwer讲过)。这个题其实涉及到一些新的知识,叫做点双连通分量(概念很简单,就是在一张连通的无向图中,对于两个点u和v,如果无论删去哪条边(只能删去一条)都不
能使它们不连通,我们就说u和v边双连通)。
这个题就是点双模板+组合数学。点双模板一会儿代码里会有。组合数学是统计答案的时候用的。说得这么玄乎,其实就是Tarjan跑出割点,然后DFS跑连通块,计算每个连通块中的割点数目,再分类
讨论即可(组合数学统计答案)。
分类讨论(对于每个连通块):
(1)没有割点:那至少要建立两个出口,注意是要从任意非割点的地方选择两个点建立。方案数为\(ans=ans\times cnt\times (cnt-1)/2\)(玄学的组合数学,感性理解一下)。
(2)有一个割点,那只要在这个连通块内任意一个非割点的地方设立一个出口即可。方案数为\(ans=ans\times cnt\)。(又是玄学的组合数学,感性理解)。
(3)如果有两个及以上个割点,那就无需建立出口,因为无论如何都可以直接到达其他连通块。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 505
typedef long long ll;
int n, m, rt, kid, cnt;
int dfn[MAXN], low[MAXN];
int vis[MAXN];
bool cut[MAXN],flag[MAXN];
int num, tot, res1, T, cs;
ll res2;
class node{
public:
int to;
node *nxt=NULL;
} edge[MAXN << 1], *head[MAXN];
class Stack{
private:
int stk[MAXN], top;
public:
inline void Push(int x) { stk[++top] = x; return; }
inline void Pop(void) { --top; return; }
inline int Top(void) { return stk[top]; }
} Stk;//这里习惯性加上了个栈,但是没用
inline int read(void){
int f = 1, x = 0;char ch;
do{ch = getchar();if(ch=='-')f = -1;} while (ch < '0' || ch > '9');
do{ x = x * 10 + ch - '0';ch = getchar();} while (ch >= '0' && ch <= '9');
return f * x;
}
inline int _min(int x,int y) { return x < y ? x : y; }
inline int _max(int x,int y) { return x > y ? x : y; }
inline void _init(void){
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(cut, 0, sizeof(cut));
memset(vis, 0, sizeof(vis));
cnt = tot = n = res1 = T = 0;
res2 = 1;//有乘法,所以初始化是1
return;
}//多组数据千万不要忘了初始化。
inline void add_edge(int x,int y){
++cnt;
edge[cnt].nxt = head[x];
head[x] = &edge[cnt];
head[x]->to = y;
return;
}
void tarjan(int k,int fa){
dfn[k] = low[k] = ++tot;
for (node *i = head[k]; i != NULL; i = i->nxt){
int v = i->to;
if (!dfn[v]){
tarjan(v, k);
low[k] = _min(low[k], low[v]);
if (low[v]>=dfn[k]){
if(k==rt) ++kid;
else cut[k] = 1;
}
}
else if(v!=fa) low[k] = _min(low[k], dfn[v]);
}
return;
}//tarjan求割点
void dfs(int x){
vis[x] = T;//为了省去每次memset的时间耗费,这里使用时间戳
if(cut[x]) return;
++cnt;
for (node *i = head[x]; i != NULL; i = i->nxt){
int v = i->to;
if(cut[v]&&vis[v]!=T)
++num, vis[v] = T;
if(!vis[v]) dfs(v);
}
}//DFS找连通块
int main(){
m=read();
while (m){
_init();//初始化
for (int i = 1; i <= m; i++){
int u = read(), v = read();
n = _max(n, _max(u, v));//别忘了这个
add_edge(u, v), add_edge(v, u);
}
for (int i = 1; i <= n; i++){
if (!dfn[i]) tarjan(rt = i,0);
if (kid>=2) cut[rt] = 1;//别忘了判断在搜索树中儿子数量>=2的情况,这里写在外面了
kid = 0;//别忘了清空
}
for (int i = 1; i <= n; i++){
if (!vis[i]&&!cut[i]){
++T, cnt = num = 0;//清空,时间戳++
dfs(i);
if (!num) res1 += 2, res2 *= cnt * (cnt - 1) / 2;//分类讨论1
if (num==1) ++res1, res2 *= cnt;//分类讨论2
}
}
printf("Case %d: %d %lld\n",++cs,res1,res2);
m=read();
}
return 0;
}

浙公网安备 33010602011771号