一、题目
下图转自“英式没品笑话百科”的新浪微博 —— 所以无论有没有遇到难题,其实都不用担心。
博主将这种逻辑推演称为“逻辑自洽”,即从某个命题出发的所有推理路径都会将结论引导到同一个最终命题(开玩笑的,千万别以为这是真正的逻辑自洽的定义……)。现给定一个更为复杂的逻辑推理图,本题就请你检查从一个给定命题到另一个命题的推理是否是“逻辑自洽”的,以及存在多少种不同的推理路径。例如上图,从“你遇到难题了吗?”到“那就别担心了”就是一种“逻辑自洽”的推理,一共有 3 条不同的推理路径。
输入格式:
输入首先在一行中给出两个正整数 N(1<N≤500)和 M,分别为命题个数和推理个数。这里我们假设命题从 1 到 N 编号。
接下来 M 行,每行给出一对命题之间的推理关系,即两个命题的编号 S1 S2,表示可以从 S1 推出 S2。题目保证任意两命题之间只存在最多一种推理关系,且任一命题不能循环自证(即从该命题出发推出该命题自己)。
最后一行给出待检验的两个命题的编号 A B。
输出格式:
在一行中首先输出从 A 到 B 有多少种不同的推理路径,然后输出 Yes 如果推理是“逻辑自洽”的,或 No 如果不是。题目保证输出数据不超过1e9.
输入样例 1:
7 8
7 6
7 4
6 5
4 1
5 2
5 3
2 1
3 1
7 1
输出样例 1:
3 Yes
输入样例 2:
7 8
7 6
7 4
6 5
4 1
5 2
5 3
6 1
3 1
7 1
输出样例 2:
3 No
二、思路
图的存储、图的遍历。
1.求从 A 到 B 有多少种不同的推理路径:
采用深度优先遍历;编写深度优先遍历函数DFS(MGraph Graph, Vertex v),以V为出发点对邻接矩阵存储的图Graph进行DFS搜索。用数组 path[v] 记录结点v到终点(这里可以理解为汇点,或者“逻辑自洽终点”)的路径条数。
2.判断图是否属于“逻辑自洽”:
就是判断是否所有的点都可以连通到汇点。用visited[v]标记当前结点是否已被访问,用path[v] 记录结点v到汇点路径条数。若存在某个结点,被访问过,但其路径条数=0,说明该点与汇点不连通。若存在这样的点,则图不满足“逻辑自洽”。
三、代码
方法一:图的存储方式采用邻接矩阵。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
using namespace std;
#define MaxVertexNum 600 /* 最大顶点数设为200 */
typedef int Vertex; /* 用顶点下标表示顶点,为整型 */
typedef int WeightType; /* 边的权值设为整型 */
/* 边的定义 */
typedef struct ENode* PtrToENode;
struct ENode {
Vertex v1, v2; /* 有向边<V1, V2> */
WeightType weight; /* 权重 */
};
typedef PtrToENode Edge;
/* 图结点的定义 */
typedef struct GNode* PtrToGNode;
struct GNode {
int Nv; /* 顶点数 */
int Ne; /* 边数 */
WeightType G[MaxVertexNum][MaxVertexNum]; /* 邻接矩阵 */
};
typedef PtrToGNode MGraph; /* 以邻接矩阵存储的图类型 */
/*函数声明*/
MGraph CreateGraph(int VertexNum);
void InsertEdge(MGraph Graph, Edge E);
MGraph BuildGraph();
int DFS(MGraph Graph, Vertex v);
int visited[MaxVertexNum];
int path[MaxVertexNum];
int main() {
MGraph graph = BuildGraph();
int begin1, end1;
scanf("%d %d", &begin1, &end1);
path[end1] = 1;
int cnt = DFS(graph, begin1);
int flag = 1;
//如果有访问过的结点,但其到目标节点的路径数为0,则不满足“逻辑自洽”
for (int i = 0; i < graph->Nv; i++) {
if (visited[i] && !path[i]) {
flag = 0;
break;
}
}
printf("%d ", cnt);
if (!flag) {
printf("No\n");
}
else {
printf("Yes\n");
}
return 0;
}
/* 初始化一个有VertexNum个顶点但没有边的图 */
MGraph CreateGraph(int VertexNum)
{
Vertex v, u;
MGraph Graph;
Graph = (MGraph)malloc(sizeof(struct GNode)); /* 建立图 */
Graph->Nv = VertexNum;
Graph->Ne = 0;
/* 初始化邻接矩阵 */
for (v = 1; v <= Graph->Nv; v++) {
for (u = 1; u <= Graph->Nv; u++) {
Graph->G[v][u] = NULL;
}
}
return Graph;
}
/* 插入边 <V1, V2> */
void InsertEdge(MGraph Graph, Edge E)
{
Graph->G[E->v1][E->v2] = E->weight;
/* 若是无向图,还要插入边<V2, V1> */
//Graph->G[E->v2][E->v1] = E->weight;
}
MGraph BuildGraph()
{
MGraph Graph;
Edge E;
int Nv, i;
scanf("%d", &Nv); /* 读入顶点个数、边的个数 */
Graph = CreateGraph(Nv); /* 初始化有Nv个顶点但没有边的图 */
scanf("%d", &(Graph->Ne)); /* 读入边数 */
if (Graph->Ne != 0) { /* 如果有边 */
E = (Edge)malloc(sizeof(struct ENode)); /* 建立边结点 */
/* 读入边,格式为"起点 终点 权重",插入邻接矩阵 */
for (i = 0; i < Graph->Ne; i++) {
scanf("%d %d", &E->v1, &E->v2);
E->weight = 1;
InsertEdge(Graph, E);
}
}
return Graph;
}
int DFS(MGraph Graph, Vertex v)
{ /* 以V为出发点对邻接矩阵存储的图Graph进行DFS搜索 */
visited[v] = 1;/* 标记V已访问 */
if (path[v]) {
return path[v];
}
/* 递归访问 */
for (int u = 1; u <= Graph->Nv; u++) {
if (Graph->G[v][u]) {
path[v] = path[v] + DFS(Graph, u);
}
}
return path[v];
}
方法二:图的存储方式采用邻接表。
思路完全相同,但由于图的存储方式不同,具体实现也会不同。(对边的遍历方式不同)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
using namespace std;
#define MaxVertexNum 600 /* 最大顶点数设为200 */
typedef int Vertex; /* 用顶点下标表示顶点,为整型 */
typedef int WeightType; /* 边的权值设为整型 */
typedef int Elementype;
/* 边的定义 */
typedef struct ENode* PtrToENode;
struct ENode {
Vertex v1, v2; /* 有向边<V1, V2> */
int weight; /* 权重 */
};
typedef PtrToENode Edge;
/* 邻接点的定义 */
typedef struct AdjVNode* PtrToAdjVNode;
struct AdjVNode {
Vertex AdjV; /* 邻接点下标 */
WeightType weight; /* 边权重 */
PtrToAdjVNode next; /* 指向下一个邻接点的指针 */
};
/*顶点表头结点的定义*/
typedef struct VNode {
PtrToAdjVNode firstEdge;//边表头指针
}AdjList[MaxVertexNum]; //AdjList是邻接表类型
/*图结点的定义*/
typedef struct GNode* PtrToGNode;
typedef PtrToGNode LGraph;
struct GNode {
int Nv;//顶点数
int Ne;//边数
AdjList G;//邻接表
};
int visited[MaxVertexNum];
int path[MaxVertexNum];
/* 初始化一个有VertexNum个顶点但没有边的图 */
LGraph CreateGraph(int VertexNum)
{
Vertex v;
LGraph Graph;
Graph = (LGraph)malloc(sizeof(struct GNode)); /* 建立图 */
Graph->Nv = VertexNum;
Graph->Ne = 0;
/* 初始化邻接表头指针 */
for (v = 1; v <= Graph->Nv; v++) {
Graph->G[v].firstEdge = NULL;
}
return Graph;
}
/* 插入边 <V1, V2>,有向图 */
void InsertEdge(LGraph Graph, Edge E)
{
PtrToAdjVNode newNode;
/*插入边,为V2建立新的邻接点*/
newNode = (PtrToAdjVNode)malloc(sizeof(struct AdjVNode));
newNode->AdjV = E->v2;
newNode->weight = E->weight;
/*将v2插入v1的表头*/
newNode->next = Graph->G[E->v1].firstEdge;
Graph->G[E->v1].firstEdge = newNode;
}
LGraph BuildGraph()
{
LGraph Graph;
Edge E;
int Nv, i;
PtrToAdjVNode w;
scanf_s("%d", &Nv); /* 读入顶点个数 */
Graph = CreateGraph(Nv); /* 初始化有Nv个顶点但没有边的图 */
scanf_s("%d", &(Graph->Ne)); /* 读入边数 */
if (Graph->Ne != 0) { /* 如果有边 */
E = (Edge)malloc(sizeof(struct ENode)); /* 建立边结点 */
/* 读入边,格式为"起点 终点",插入邻接矩阵 */
for (i = 0; i < Graph->Ne; i++) {
scanf_s("%d %d", &E->v1, &E->v2);
E->weight = 1;
InsertEdge(Graph, E);
}
}
return Graph;
}
int DFS(LGraph Graph, Vertex v)
{ /* 以V为出发点对邻接表存储的图Graph进行DFS搜索 */
PtrToAdjVNode w;
if (path[v]) {
return path[v];
}
visited[v] = 1; /* 标记V已访问 */
w = Graph->G[v].firstEdge;
while (w) {
if (!visited[w->AdjV]) {
path[v] = path[v] + DFS(Graph, w->AdjV);
w = w->next;
}
}
return path[v];
}
int main() {
LGraph graph = BuildGraph();
memset(visited, 0, sizeof(visited));
memset(path, 0, sizeof(path));
int begin1, end1;
scanf_s("%d %d", &begin1, &end1);
path[end1] = 1;
int cnt = 0;
cnt = DFS(graph, begin1);
int flag = 1;
for (int i = 1; i <= graph->Nv; i++) {
if (visited[i] && !path[i]) {
flag = 0;
break;
}
}
printf("%d ", cnt);
if (!flag) {
printf("No\n");
}
else {
printf("Yes\n");
}
return 0;
}