例题6-19 UVA1572 Self-Assembly(39行AC代码)

紫书刷题进行中,题解系列【GitHub|CSDN

例题6-19 UVA1572 Self-Assembly(39行AC代码)

题目大意

锻炼思维的好题,注重分析思路,等价条件转换推理哦

给定n种正方形,每种正方形有4条边,每条边有2个字符构成,包含以下两种模式:

  • 第一个为A-Z的大写字母,第二个为+/-,如A+或Z-
  • 00

当两条边的第一个字符相同,而第二个字符相反时,两边可相连,00不可与任何边相连。

现假设每种正方形无限供应,问是否存在无限拼接图像?

思路分析

一拿到题目,毫无头绪,但可以知道它是一个判定性问题,即只需回答是/否

而细看要求,要求判定是否存在无限延展的图像,若是存在的话,计算机是没法直接构造出无限的图像的,那么它在暗示我们肯定可以找到一个判定无限的等价条件(类似于循环节的寻找,比如循环小数UVA202和死循环判定UVA12108

按着循环节思路继续分析下去,发现既然是图像,那么能不能用图论方式解决?

  • 尝试1:将正方形当成顶点,遍历查找可用正方形判断是否有能够连接的。但存在一个致命问题,可用正方形个数无数,组合方式爆炸,除非我能穷举每种情况,才能证明它不存在无限的情况,又绕回了思路分析的源点
  • 尝试2:将每个正方形的边当成顶点,将正方形作为沟通各个点的边(这里需要一点想象力,和直觉违背),若通过以上方式构建的图存在有向环(循环节),说明存在无限的可能。举个构建图的例子:
图一共有A-Z(+/-)52个顶点

输入:K+K-Q+Q-
对于边K+,构造出一点顶点K1,可想而知,只有另一个正方形的边为K-时才能与K+相邻,间接与剩余的3条边相邻,转换为图即是K-这个点能单向到达K-,Q+,Q-3个点。
> 思考:想想为何不构造K+到K-的边?

同理,对于边K-,构造顶点K+到K+,Q+,Q-单向边
对于边Q+,构造顶点Q-到K+,K-,Q-的单向边
对于边Q-,构造顶点Q+到K+,K-,Q+的单向边

至此,思路基本明了,将复杂问题转换为有向图的有向环判断问题,有两种思路处理有向环,可以说图的算法核心就两种:bfs和dfs。这里也一样

  • 拓扑排序(bfs):拓扑排序过程中计算出队的顶点个数,若不为52,说明存在有向环
  • dfs标记法:关键在于访问状态设计,用0:违访问, -1:当前正在访问, 1:已访问来标记状态,在遍历过程中碰到-1的状态,表示遇到环。以下为二者对求环问题的对比
对比项目\处理方法 bfs(拓扑排序) dfs(活用标记)
适用性 有向图 任意图
打印环
效率 低(递归用栈消耗大,防止爆栈)

算法设计

为了便于处理A+和A-的转换关系,定义以下转换函数(类似哈希函数),令二者处于相邻位置,到真正访问与1异或即可完成变换,例如ID(A+)=0,ID(A-)=1,那么将ID(A+)^1=1即可得到ID(A-)

int getId(char c1, char c2) { // 获取字符节点的编号
    return (c1 - 'A')*2 + ((c2 == '+') ? 0 : 1); // 注意?:优先级很低,要用括号
}

图用邻接表方式存储,若是用dfs方式判环,必须注意去除重复邻边,bfs方式不影响

其余细节参见详细的代码注释,以下给出两种思路的AC代码(二者仅判环过程不同,其余均一致)

AC代码(C++11)

DFS(标记活用)

#include<bits/stdc++.h>
using namespace std;
vector<set<int> > adj; // 邻接表,注意用set存储去重
int vis[52]={0}; // 标记访问数组
int getId(char c1, char c2) { // 获取字符节点的编号
    return (c1 - 'A')*2 + ((c2 == '+') ? 0 : 1); // 注意?:优先级很低,要用括号
}
void connect(char a1, char a2, char b1, char b2) { // 参数分别表示两点的第一二个字符
    if (a1 == '0' || b1 == '0') return; // 任意一点为0均不连接
    adj[getId(a1,a2)^1].insert(getId(b1,b2)); // 有向图构建;异或含义-> (B+)^1=B-
}
bool dfs(int u) { // 判断以顶点u开始是否存在有向图
    vis[u] = -1; // 当前遍历的标记
    for (int v : adj[u]) // 邻边
        if (vis[v] == -1 || (vis[v] == 0 && dfs(v))) return true; // 碰见当前已访问节点,表示存在有向环
    vis[u] = 1; // 回溯时标记为1,表示已访问过,必须在return false之前
    return false; // 到此处表示当前连通块不存在有向环
}
bool find_cycle() { // 检查图是否存在有向环
    memset(vis, 0, sizeof(vis)); // 初始化为0,表示未访问
    for (int i=0; i < 52; i ++) // 遍历所有连通块
        if (vis[i] == 0 && dfs(i)) return true; // 存在一个环
    return false;
}
int main() {
    int n; string s;
    while (cin >>n) {
        adj.clear(); adj.resize(52); // 初始化
        for (int i=0; i < n; i ++) {
            cin >>s;
            for (int i=0; i < 4; i ++) { // 考虑旋转翻转
                for (int j=0; j < 4; j ++)
                    if (i != j) connect(s[i*2],s[i*2+1],s[j*2],s[j*2+1]); // 构建有向图
            }
        }
        printf("%s\n", !find_cycle() ? "bounded" : "unbounded");
    }
    return 0;
}

BFS(拓扑排序)

#include<bits/stdc++.h>
using namespace std;
vector<vector<int> > adj; // 邻接表
int getId(char c1, char c2) { // 获取字符节点的编号
    return (c1 - 'A')*2 + ((c2 == '+') ? 0 : 1); // 注意?:优先级很低,要用括号
}
void connect(char a1, char a2, char b1, char b2) { // 参数分别表示两点的第一二个字符
    if (a1 == '0' || b1 == '0') return; // 任意一点为0均不连接
    adj[getId(a1,a2)^1].push_back(getId(b1,b2)); // 有向图构建;异或含义-> (B+)^1=B-
}
bool bfs() { // 拓扑排序检测是否存在有向环
    int indegree[52]={0}, num=0; // 访问数组, 入度表,入队计算
    for (int i=0; i < 52; i ++) 
        for (auto v : adj[i]) indegree[v] ++; // 入度计算
    queue<int> q;
    for (int i=0; i < 52; i ++) if (indegree[i] == 0) q.push(i); // 队列初始化
    while (!q.empty()) {
        int u=q.front(); q.pop(); num ++; // 统计出队个数
        for (int v : adj[u]) {
            indegree[v] --; // 更新入度表
            if (indegree[v] == 0) q.push(v); // 入度=0则进队
        }
    }
    return num == 52; // 不存在有向环num=52
}
int main() {
    int n; string s;
    while (cin >>n) {
        adj.clear(); adj.resize(52); // 初始化
        for (int i=0; i < n; i ++) {
            cin >>s;
            for (int i=0; i < 4; i ++) { // 考虑旋转翻转
                for (int j=0; j < 4; j ++)
                    if (i != j) connect(s[i*2],s[i*2+1],s[j*2],s[j*2+1]); // 构建有向图
            }
        }
        printf("%s\n", bfs() ? "bounded" : "unbounded");
    }
    return 0;
}
发布了128 篇原创文章 · 获赞 87 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_40738840/article/details/104419012