情景描述:
在某片遥远的大陆上,居住着两个世代友好的部落,分别是部落A和部落B。他们一起耕耘劳作,互相帮助,亲如一家。久而久之,部落里的每个人都在对方部落里找到了志趣相投,互相欣赏的好朋友。有的人性格热情开朗,好朋友很多;有的人性格沉稳内敛,好朋友相对少一些。
每到秋天丰收的季节,这两个部落的人民都会聚集在一起举行盛大的“丰收祭”,来祈祷下一年的风调雨顺。今年的丰收祭马上又要举行了。为了进一步增进两个部落的友谊,也为了明年能有一个好收成,这两个部落的祭司们商量后决定:在今年的丰收祭前举办一场特别的“击鼓传花”游戏。只不过游戏中并非有人真的击鼓,并且所传递的“花”也不是真的花,而是等待在丰收祭上献上的祭品。
游戏规则如下:
- 两个部落的所有人都可以事先准备自己的祭品,且每个人的祭品样式都不同,每一个祭品都分别盛放在一个相对应的木托盘里;准备此祭品的人熟悉自己的祭品;
- 每个人可以准备的祭品数量不限;祭品的最小不可分割单位是1份;
- 游戏开始后,在整个游戏过程中,每个人都能且只能将祭品(包括木托盘)传递给自己在对方部落里的好朋友们,每个好友可以接收的祭品数量不限;
- 收到祭品的人必须在盛放此祭品的木托盘上刻上自己的名字(代表留下自己美好的祝愿),随后按按照上一条规则,继续传递;
- 如果祭品回到最初准备此祭品的人手中,此人也在木托盘上刻上自己的名字之后,终止传递;
- 木托盘上不允许出现重复的人名,如果无法满足此条件,则不再继续传递该祭品;
- 当所有的祭品都不再传递后,游戏结束;
游戏开展得非常顺利。游戏结束后,祭司们将收集同时满足如下三个条件的祭品用于接下来的丰收祭活动:
- 此祭品回到了最初准备它的人手中;
- 盛放此祭品的木托盘上至少有4个名字,至多有14个名字;
- 如果有多个木托盘上的名字完全一样(不区分名字的排列顺序),则从其中随机选择一个木托盘所对应的祭品。
问题:
已知这两个部落里的所有人都不重名,并且部落A的人和部落B的人之间的好朋友关系以附件的csv数据表格文件给出,其中行索引代表部落A中的人,列索引代表部落B中的人,表格中的数字“1”代表他们两人是好朋友,“0”代表他们两人不是好朋友。请问:
如果以木托盘上的名字的数量对用于丰收祭的祭品分类,每一类分别最多有多少个祭品?
思考:
这是一个典型的二部图,但是好像没做过针对二部图的问题,在我的知识领域只能把问题普遍化,看成一个图的问题。
通过csv构造一个图,找出所有的简单环路,保证节点数在题目要求范围内。
然后开始了编码,基本思路是深搜,在所有通路中找到所有的环,然后去重并保存在vector里,代码如下
#include "pch.h"
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <algorithm>
#include <set>
#include <iterator>
#include <cstring>
using namespace std;
const int MAXROW = 256;//样例数据行数
const int MAXCOL = 640;//样例数据列数
const int MAXN = MAXROW + MAXCOL;//最大节点数,编号按照先行(第一个部落)后列(第二个部落)存储,一共就是所有部落的人数。
//const int MAXN = MAXROW + MAXCOL + 5;//数组加5,开大一点保险
vector<int> nodes[MAXN];//记录节点连接情况
bool visited[MAXN];//记录节点访问情况
vector<int> path;//目前的访问的点的路径
vector<set<int>> circles;//记录最终所有的环
int result[15];//存储长度为14以内的环的个数
//读取csv文件,获取节点连接情况
void readCsv(string path)
{
ifstream fin(path);
if (fin.fail())
{
cout << "File not found" << endl;
return;
}
string line; //临时存储一行字符串
int row_index = 0; //行序号
while (getline(fin, line) && fin.good()) //整行读取
{
istringstream sin(line); //将整行字符串line读入到字符串流istringstream中
string tempUnit; //临时存放每一个单元的数据
int col_index = 0; //列序号
while (getline(sin, tempUnit, ',')) //将字符串流sin中的字符读入到tempUnit中,以逗号为分隔符
{
if (tempUnit == "1")
{
nodes[row_index].push_back(col_index + MAXROW);
nodes[col_index + MAXROW].push_back(row_index);
}
col_index++;
}
row_index++;
}
fin.close();
return;
}
//打印图
void printNodes()
{
for (int i = 0;i < MAXN;i++)
{
cout << "第" << i << "号人的朋友号码有:";
for (int j = 0;j < nodes[i].size();j++)
{
cout << nodes[i][j] << " ";
}
cout << endl;
}
}
//深搜
void dfs(int start, int pre)
{
path.push_back(start);
visited[start] = true;
for (int i = 0;i < nodes[start].size();i++)
{
if (!visited[nodes[start][i]])
{
dfs(nodes[start][i], start);
}
else if (nodes[start][i] != pre)
{
//找到非二节点环,记录
int j_index = -1;
for (int j = 0;j < path.size();j++)
{
if (path[j] == nodes[start][i])
j_index = j;
}
if (j_index == -1)
cout << "Not Found" << endl;
else
{
int circle_len = path.size() - j_index;
if (circle_len <= 14)
{
set<int> temp;
bool sameflag = false;
cout << "存在<=14节点的环:";
for (int k = j_index;k < path.size();k++)
{
cout << path[k] << " ";
temp.insert(path[k]);
}
for (int k = 0;k < circles.size();k++)
{
if (circles[k] == temp)
{
sameflag = true;
break;
}
}
if (!sameflag)
{
circles.push_back(temp);
result[circle_len]++;
cout << "未重复,计入总数" << endl;
}
else
{
cout << "重复,不计入总数" << endl;
}
}
/*else
{
cout << "发现节点数为"<< circle_len <<"的环,不符要求,不计入"<<endl;
}*/
}
}
}
path.pop_back();
visited[start] = false;
}
//打印结果
void printResult()
{
cout << "总计如下:" << endl;
for (int i = 0;i < 15;i++)
{
cout << "节点数为" << i << "的环有" << result[i] << "个" << endl;
}
cout << endl;
}
//测试
void test()
{
//数据来源:
//https://blog.csdn.net/robin_xu_shuai/article/details/51898847
nodes[0].push_back(9);
nodes[1].push_back(2);
nodes[1].push_back(6);
nodes[2].push_back(1);
nodes[2].push_back(3);
nodes[2].push_back(5);
nodes[3].push_back(2);
nodes[3].push_back(4);
nodes[3].push_back(5);
nodes[4].push_back(3);
nodes[4].push_back(5);
nodes[5].push_back(2);
nodes[5].push_back(4);
nodes[5].push_back(6);
nodes[5].push_back(3);
nodes[6].push_back(1);
nodes[6].push_back(5);
nodes[6].push_back(7);
nodes[6].push_back(9);
nodes[7].push_back(6);
nodes[7].push_back(8);
nodes[8].push_back(7);
nodes[8].push_back(9);
nodes[9].push_back(0);
nodes[9].push_back(6);
nodes[9].push_back(8);
}
//初始化
void init()
{
for (int i = 0; i < MAXN; i++) {//初始化nodes
nodes[i].clear();
}
memset(visited, false, sizeof(visited));
memset(result, 0, sizeof(result));
path.clear();
}
int main()
{
init();
readCsv("Example4-08.csv");
//readCsv("test.csv");
//test();//测试的时候先把 int MAXN = 10;
printNodes();
dfs(0, -1);
printResult();
system("pause");
return 0;
}
但第一个版本的代码运行了很久也没结果,并通过输出中间结果看,是因为递归深度太深,时间花费巨大,因此考虑剪枝,路径超过14的时候不向下递归,这样只在14节点里找环,但这样就不能只从dfs(0)开始就可以找齐,就需要每个节点都dfs一下,于是产生了第二版本的代码
但这个版本还是时间太长,就不贴了。是因为尽管我们只考虑14节点里找环会漏掉一些环,但也不至于for循环进行dfs,这样循环一圈比不循环找到的重复的环可能远远超过了不循环漏下的部分。但我又跑了一遍不循环的(存在遗漏)版本,时间还是太长。我痛改前非,是不是我策略出现问题了,可能是要利用二部图的特点吗?仔细看题目下边这样说:“留意观察数据的特征,参赛者也许可以找到提升效率的思路。” 不禁陷入深思。。。。可能只有等比赛结束才能向榜上的大佬取经了,不过我也想再挣扎一下。
结束
比赛结束了,来取经,榜上有名的大佬们果然思路各种trick,虚心学习,不重复造轮子了。