LuoguP3225[HNOI2012]矿场搭建
[题目传送门]
思路的话其他大佬都已经讲的很清楚了qwq 我主要是想再提供一种写法
其实是自己太菜看不懂其他巨佬的代码QAQ
代码里也有一些解释,主要就是要会点双联通分量(代码里的 点分 )就好了
献上蒟蒻的代码 Code:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e4 + 7;
int n, m;
int tot, head[N];
bool cut[N];//记录一个点是不是割点
vector<int> dcc[N];//记录每个点双里面点
int id, top, vdcc, dfn[N], low[N], stk[N];
struct edge{
int net, to;
}e[N * 3];
void add(int u, int v) {
e[++tot] = {head[u], v};
head[u] = tot;
}
void tarjan(int u, int fa) {//求点分模板
dfn[u] = low[u] = ++id;
stk[++top] = u;
if (u == fa && !head[u]) {//单独的点就计入单独的点分
dcc[++vdcc].push_back(u);
return;
}
int flag = 0;
for (int i = head[u]; i; i = e[i].net) {
int to = e[i].to;
if (!dfn[to]) {
tarjan(to, u);
low[u] = min(low[u], low[to]);
if (low[to] >= dfn[u]) {
flag++;
if (u != fa || flag > 1) {
cut[u] = true;//记下割点
}
vdcc++;
dcc[vdcc].clear();//多组数据要清空
int y;
do {
y = stk[top--];
dcc[vdcc].push_back(y);
} while (y != to);//注意这里是to
dcc[vdcc].push_back(u);
}
} else {
low[u] = min(low[u], dfn[to]);
}
}
}
int main () {
int Case = 0;
while (scanf("%d", &m) && m != 0) {
Case++;
ll ans1 = 0, ans2 = 1;
vdcc = tot = top = id = 0;
memset(head, 0, sizeof head);
memset(cut, 0, sizeof cut);
memset(dfn, 0, sizeof dfn);
//注意一下多组数据的清空,low数组会被id更新,不用清
n = 0;
for(int i = 1; i <= m; i++) {
int u, v;
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
n = max(n, max(u, v));//题里面没说点标号就只好自己取max
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) {
tarjan(i, i);
}
}
for (int i = 1; i <= vdcc; i++) {
int cutnum = 0;
int siz = (int)dcc[i].size();
for (int j = 0; j < siz; j++) {
if (cut[dcc[i][j]]) {
cutnum++;//记录该点分之中的割点数
}
}
if (siz == 1) {//单独的点特判一下
ans1++;
continue;
}
if (cutnum == 1) {
ans1++;
ans2 *=(siz - 1);
//如果只有一个割点的话,必须要再建一个非割点的出口
//这样才能保证在割点坍塌之后该点分还能出去
} else if (!cutnum) {
ans1 += 2;
ans2 *= siz * (siz - 1) / 2;
//如果没有割点的话就必须建两个出口,然后就是C(n,2),化简后就是n*(n-1)/2
}
//还有一种情况就是有两个割点
//此时无论哪个点塌了,该点分里的点都能有地方出去,所以不需要建出口了
}
printf("Case %d: %lld %lld\n", Case, ans1,ans2);
}
return 0;
}
蒟蒻也是才开始看双联通分量,如果有不妥的地方还请巨佬们指出
感谢观赏