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

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

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

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

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

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

#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;
}


你可能感兴趣的:(UVa 818 - Cutting Chains(枚举子集+回溯+DFS))