三倍經驗
UVA1108 Mining Your Own Business
SP16185 BUSINESS - Mining your own business
題目大意
煤礦工地可以看成是由隧道連接挖煤點組成的無向圖。為安全起見,希望在工地發生事故時所有挖煤點的工人都能有一條出路逃到救援出口處。於是礦主決定在某些挖煤點設立救援出口,使得無論哪一個挖煤點坍塌之后,其他挖煤點的工人都有一條道路通向救援出口。 請寫一個程序,用來計算至少需要設置幾個救援出口,以及不同最少救援出口的設置方案總數。
數據范圍:
\(n \leq 500\),輸入數據保證答案小於 \(2^{64}\)。
解題思路
顯然,先 Tarjan
跑出割點,然后 DFS
搜索所有的聯通塊(也可以直接跑點雙,不過我考場時是這樣寫的)。
計算每一個聯通塊中的割點數目。
然后再分類討論:
- 如果沒有割點,至少需要建立兩個出口,從任意地方選擇兩個點建立
,如果設連通塊的大小為 \(siz\),則對答案的貢獻為 \(\frac{(siz-1)*siz}{2}\)。- 如果轟炸其中一個出口,則其他點都可以從另一個出口逃走。
- 如果轟炸的是其他點,不啥事都沒有嗎~
- 如果這個連通塊只有一個割點,只需要在連通塊內設立一個出口,可以設立在任意一個非割點的地方,如果設連通塊的大小為 \(siz\),則貢獻為 \(siz-1\)(因為不能建立在割點上)。
- 如果轟炸的是割點,則連通塊內的其他點都可以通過剛剛建立的出口出去。
- 如果轟炸的是我們剛剛建立的出口,那由於我們剛剛建立的出口不是割點,所以連通塊內除了轟炸了的點,都可以從割點通向另一個連通塊內的出口。
- 如果這個連通塊有兩個及以上個割點,則不需要建立出口。
- 如果轟炸的是其中一個割點,則可以通過其他割點到達其他的連通塊。
- 如果轟炸的是其他點,則轟炸的不是割點,啥事都沒有~
最后將所有連通塊的貢獻乘起來(簡單的乘法原理)。
如果有兩個及以上個割點,則無需建立,可以直接到達其他聯通塊
AC CODE
邊雙聯通分量
#include <bits/stdc++.h>
using namespace std;
#define _ 905
long long Case = 1;
long long n, m, Ans1, Ans2;
long long Cut, cnt_node, cntn, root;
int dfn[_], vis[_], low[_], siz[_];
stack<int> s;
bool cut[_];
int head[_], cnt;
struct Node
{
int v, next;
} e[_ * _];
vector<int> cutt[_];
void add(int u, int v)
{
e[++cnt] = (Node)
{
v, head[u]
};
head[u] = cnt;
}
void Tarjan(int u, int fa)
{
dfn[u] = low[u] = ++cnt_node;
s.push(u);
int flag = 0;
if(u == root && !head[u])
{
cutt[++cntn].push_back(u);
return;
}
for (int i = head[u]; i; i = e[i].next)
{
int v = e[i].v;
if (!dfn[v])
{
Tarjan(v, u);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u])
{
flag++;
if(u != root || flag > 1) cut[u] = 1;
cntn++;
while(1)
{
int now = s.top();
s.pop();
cutt[cntn].push_back(now);
if(now == v) break;
}
cutt[cntn].push_back(u);
}
}
else low[u] = min(low[u], dfn[v]);
}
}
void init()
{
memset(head, 0, sizeof(head));
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(cut, 0, sizeof(cut));
memset(vis, 0, sizeof(vis));
for(int i = 0; i < _; ++i) cutt[i].clear();
cnt_node = 0;
cnt = 0;
n = 0;
Ans1 = 0;
Ans2 = 1;
cntn = 0;
while(!s.empty()) s.pop();
}
signed main()
{
while (cin >> m && m)
{
init();
for (long long i = 1; i <= m; ++i)
{
long long u, v;
scanf("%lld%lld", &u, &v);
add(u, v);
add(v, u);
n = max(n, max(u, v));
}
for (int i = 1; i <= n; ++i)
if (!dfn[i]) Tarjan(root = i, i);
int opt = 0;
for (int i = 1; i <= cntn; ++i)
{
Cut = 0;
for(int j = 0; j < cutt[i].size(); ++j)
if(cut[cutt[i][j]]) Cut++;
if (Cut == 0)
{
Ans1 += 2;
Ans2 *= (long long)(cutt[i].size() - 1) * cutt[i].size() / 2;
}
if (Cut == 1)
{
Ans1++;
Ans2 *= (long long)cutt[i].size() - 1;
}
}
cout << "Case " << Case++ << ": " << Ans1 << " " << Ans2 << endl;
}
return 0;
}
割點 \(+\) dfs
#include <bits/stdc++.h>
using namespace std;
#define _ 905
long long Case = 1;
long long n, m, Ans1, Ans2;
long long num, Cut, cnt_node, root;
int dfn[_], vis[_], low[_];
bool cut[_];
int head[_], cnt;
struct Node
{
int v, next;
} e[_ * _];
void add(int u, int v)
{
e[++cnt] = (Node){v, head[u]};
head[u] = cnt;
}
void Tarjan(int u, int fa)
{
dfn[u] = low[u] = ++cnt_node;
int flag = 0;
for (int i = head[u]; i; i = e[i].next)
{
int v = e[i].v;
if (!dfn[v])
{
Tarjan(v, u);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u])
{
flag++;
if(u != root || flag > 1) cut[u] = 1;
}
}
else if (v != fa)
low[u] = min(low[u], dfn[v]);
}
}
void dfs(int u, int id)
{
vis[u] = id;
num++;
for (int i = head[u]; i; i = e[i].next)
{
int v = e[i].v;
if (cut[v] && vis[v] != id)
{
Cut++;
vis[v] = id;
}
if (vis[v]) continue;
dfs(v, id);
}
}
void init()
{
memset(head, 0, sizeof(head));
memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low));
memset(cut, 0, sizeof(cut));
memset(vis, 0, sizeof(vis));
cnt_node = 0;
cnt = 0;
n = 0;
Ans1 = 0;
Ans2 = 1;
}
signed main()
{
while (cin >> m && m)
{
init();
for (long long i = 1; i <= m; ++i)
{
long long u, v;
scanf("%lld%lld", &u, &v);
add(u, v);
add(v, u);
n = max(n, max(u, v));
}
for (int i = 1; i <= n; ++i)
if (!dfn[i]) Tarjan(root = i, i);
int opt = 0;
for (int i = 1; i <= n; ++i)
{
if (!vis[i] && !cut[i])
{
num = 0;
Cut = 0;
dfs(i, ++opt);
if (Cut == 0)
{
Ans1 += 2;
Ans2 *= (long long)(num - 1) * num / 2;
}
if (Cut == 1)
{
Ans1++;
Ans2 *= (long long)num;
}
}
}
cout << "Case " << Case++ << ": " << Ans1 << " " << Ans2 << endl;
}
return 0;
}