UVa 818 - Cutting Chains(枚举子集+回溯+DFS)

给出环的数量n,之后再给出环的连接状态,问最少打开几个环再重新连接就能使所有环连成一个链条。

环的数量很少,用二进制枚举子集,然后逐个判断是否可以连成一条链,如果连成,用解开的数目维护最小值。

判断能否连成一条链,首先要使解开后的链条没有三个连在一起的结点,然后链条不能形成环。

判断形成环时,DFS判断。若从该结点出发,能回到该结点,则有环。

今天在看代码时发现个可以优化的地方,加了个回溯条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include<cstdio>
#include<cstring>
const int INF=1<<30;
const int maxn=20;
int n,best;
bool g[maxn][maxn],vis[maxn];
bool read(){
if(scanf("%d",&n)==EOF||!n) return false;
memset(g,0,sizeof(g));
int u,v;
while(~scanf("%d%d",&u,&v)&&!(u==-1&&v==-1))
g[u-1][v-1]=g[v-1][u-1]=true;
return true;
}
bool node(int s){//判断是否形成结点。
for(int i=0;i<n;++i){
if(s&(1<<i)) continue;
int num=0;
for(int j=0;j<n;++j)
if(!(s&(1<<j))&&g[i][j]) ++num;
if(num>2) return false;
}
return true;
}
bool dfs(int s,int cur,int pre)
{
vis[cur]=true;
bool ok=true;
for(int i=0;i<n;++i){
if(!(s&(1<<i))&&g[cur][i]){
if(!vis[i]){
if(!dfs(s,i,cur))
ok=false;
}
else if(i!=pre) return false;//已经来过,但不是上一个,形成环。
}
}
if(!ok) return false;
return true;
}
bool ring(int s,int num){//判断有无环。
int cnt=0;
memset(vis,0,sizeof(vis));
for(int i=0;i<n;++i){
if(s&(1<<i)) continue;
if(vis[i]) continue;
++cnt;
if(!dfs(s,i,-1)) return false;
}
if(cnt>num+1) return false;
return true;
}
int solve(){
int best=INF,s=1<<n;//二进制枚举子集,0表示不解开,1表示解开。
for(int i=0;i<s;++i){
int cur=0;
for(int j=0;j<n;++j)
if(i&(1<<j)) ++cur;
if(cur>=best) continue;//统计解开的个数,若大于之前最大值,则continue。
if(node(i)&&ring(i,cur))
best=best>cur?cur:best;
}
return best;
}
int main(){
int t=0;
while(read())
printf("Set %d: Minimum links to open is %d\n",++t,solve());
return 0;
}

本文迁移自我的 CSDN博客 ,格式可能有所偏差。