[HNOI2012]矿场搭建 解题报告
题目链接
简要题意
给一个无向图,在图中选择若干特殊点,使得在去除图中任意一个顶点后,每个连通块都有至少一个特殊点。(图中无孤立点)
解题报告
看到“去除顶点”问连通性,我们应当立马想到v-DCC。
我们分类讨论对于一个v-DCC来说,要设置几个特殊点。
- 如果这个v-DCC中没有割点;也就是说,这个v-DCC其实就是一个连通块。这时我们应当设置两个不同的特殊点。假如只设一个,那么当特殊点被去掉了就没辙了。这是答案增2,方案数乘上 ( n 2 ) \binom{n}{2} (2n)
- 如果这个v-DCC中有大于等于2个割点,那么这个v-DCC就很好办:不管你去掉哪个点,其他的v-DCC中的点都可以通过割点(即使去掉一个也至少还有一个)到其它与之相连的v-DCC。我们下面会证明,这时一个割点都不需要设。
- 如果这个v-DCC有且仅有一个割点。如果去掉一个非割点,那么v-DCC中的其他点都可以通过割点到其他v-DCC去;如果去掉割点,那么就被堵死了。所以这时要在非割点处设置一个特殊点。现在我们回到上面的问题:为何情况2一个割点都不要设?因为情况2不论砸掉哪个点,它都可以一直沿着其它的v-DCC往下找,直到找到一个情况3的v-DCC,那里一定设了一个特殊点。
至此,这题就很明朗了。先找v-DCC,然后按照割点数量去分类讨论。
代码
//...
const int MAXN = 1005;
const int MAXE = 1005;
int n, num;
map<int, int> mp;
int head[MAXN], nxt[MAXE], ver[MAXE], cnt;
int dfn[MAXN], low[MAXN], tim;
int dccnum;
int stk[MAXN], stp;
vector<int> dcc[MAXN];
bool iscut[MAXN];
ll ans, f;
void Clear() {
num = cnt = tim = dccnum = stp = ans = 0;
f = 1;
mp.clear();
memset(head, 0x00, sizeof head);
memset(dfn, 0x00, sizeof dfn);
memset(low, 0x00, sizeof low);
memset(iscut, 0x00, sizeof iscut);
for(int i = 1; i < MAXN; i++) dcc[i].clear();
}
void addedge(int u, int v) {
ver[++cnt] = v; nxt[cnt] = head[u]; head[u] = cnt;
}
int getnum(int x) {
map<int, int>::iterator k = mp.find(x);
if(k != mp.end()) return k->second;
mp[x] = ++num;
return num;
}
void tarjan(int u, int rt) {
dfn[u] = low[u] = ++tim;
stk[++stp] = u;
int fl = 0;
if(u == rt && !head[u]) {
dcc[++dccnum].push_back(u);
return ;
}
for(int i = head[u]; i; i = nxt[i]) {
int v = ver[i];
if(!dfn[v]) {
tarjan(v, rt);
low[u] = min(low[u], low[v]);
if(dfn[u] <= low[v]) {
fl++;
if(u != rt || fl > 1) iscut[u] = 1;
dccnum++;
int t;
do {
t = stk[stp--];
dcc[dccnum].push_back(t);
}while(t != v);//be careful!!! vvvvvvv
dcc[dccnum].push_back(u);
}
} else low[u] = min(low[u], dfn[v]);
}
}
void work() {
Clear();
for(int i = 1; i <= n; i++) {
int u = read(), v = read();
u = getnum(u); v = getnum(v);
addedge(u, v); addedge(v, u);
}
for(int i = 1; i <= num; i++)
if(!dfn[i]) tarjan(i, i);
for(int i = 1; i <= dccnum; i++) {
int cnt1 = 0, cnt2 = 0;
for(auto j : dcc[i]) {
if(iscut[j]) cnt1++;
else cnt2++;
}
if(cnt1 == 0) {
ans += 2;
f *= 1ll * cnt2 * (cnt2 - 1) / 2;
} else if(cnt1 == 1) {
ans += 1;
f *= cnt2;
}
}
printf("%lld %lld\n", ans, f);
}
int main() {
for(int i = 1; ; i++) {
n = read(); if(!n) return 0;
printf("Case %d: ", i);
work();
}
return 0;
}