集合划分问题
集合划分问题是对这样一类问题的抽象,即原始问题包含一些列可能结果,不断的划分出子集并研究剩余的集合能否继续划分,即问题能否最终求解,通常采取dfs进行求解
题目描述
一种卡牌游戏,规则如下:
总共有36张牌,每张牌是1~9。每个数字4张牌。
你手里有其中的14张牌,如果这14张牌满足如下条件,即算作和牌
14张牌中有2张相同数字的牌,称为雀头。
除去上述2张牌,剩下12张牌可以组成4个顺子或刻子。顺子的意思是递增的连续3个数字牌(例如234,567等),刻子的意思是相同数字的3个数字牌(例如111,777)
例如:
1 1 1 2 2 2 6 6 6 7 7 7 9 9 可以组成1,2,6,7的4个刻子和9的雀头,可以和牌
1 1 1 1 2 2 3 3 5 6 7 7 8 9 用1做雀头,组123,123,567,789的四个顺子,可以和牌
1 1 1 2 2 2 3 3 3 5 6 7 7 9 无论用1 2 3 7哪个做雀头,都无法组成和牌的条件。
现在,小包从36张牌中抽取了13张牌,他想知道在剩下的23张牌中,再取一张牌,取到哪几种数字牌可以和牌。
输入描述:
输入只有一行,包含13个数字,用空格分隔,每个数字在1~9之间,数据保证同种数字最多出现4次。
输出描述:
输出同样是一行,包含1个或以上的数字。代表他再取到哪些牌可以和牌。若满足条件的有多种牌,请按从小到大的顺序输出。若没有满足条件的牌,请输出一个数字0
示例1
输入
1 1 1 2 2 2 5 5 5 6 6 6 9
输出
9
说明
可以组成1,2,6,7的4个刻子和9的雀头
求解思路
容易知道最终的问题为:判定14张牌能否构成和牌
首先讨论哪些牌可以作为雀头(必须数量大于等于2)
然后剩下的牌能否进行这样的划分:
①对于一个数字x,其划分为重复子集合,即{x、x、x}
②对于一个数字x,其划分为连续子集合,即{x、x+1、x+2}
若能进行划分,那么剩下的牌继续执行划分逻辑,直到所有的牌划分干净
实现细节
采取map<int,int>记录每种牌的数量,采取map.count 等函数研究某种牌是否存在以及剩余数量
采取传引用方式进行dfs操作,易错点:进入下一层递归,map对应节点-=i 递归返回后 对应节点+=i
代码实现
#include<iostream>
#include<vector>
#include<map>
using namespace std;
bool dfs(map<int,int>& mp) //研究这些数字能否被划空
{
int count=0;
for(auto item:mp)
count+=item.second;
if(count==0)
return true;//已经划空了
for(auto item:mp)
{
//研究item划成重复成员
if(item.second>=3)
{
mp[item.first]-=3;
if(dfs(mp))
return true;
mp[item.first]+=3;
}
if(item.second>0 && mp.count(item.first+1)>0 && mp.count(item.first+2)>0 && mp[item.first+1]>0 && mp[item.first+2]>0) //研究划分为顺子(以item为起点)
{
mp[item.first]-=1;
mp[item.first+1]-=1;
mp[item.first+2]-=1;
if(dfs(mp))
return true;
mp[item.first]+=1;
mp[item.first+1]+=1;
mp[item.first+2]+=1;
}
}
//所有的item研究过了
return false;
}
bool func(vector<int>& arr )
{
//判断arr中是否可以和牌子
map<int,int> mp;
for(auto item:arr)
{
mp[item]++; //计数
}
//研究可能的雀头
for(auto item:mp)
{
if(item.second>=2) //首先必须大于2
{
map<int,int> mp2=mp;
mp2[item.first]-=2;//减去这两个
if(dfs(mp2))
return true;
}
}
return false;
}
int main()
{
vector<int> a;
vector<int> arr(10,4);
for(int i=0;i<13;i++)
{
int temp;
cin>>temp;
a.push_back(temp);
arr[temp]--;//对应的牌的数量减少
}
bool flag=false;
//研究剩余的牌中加入某个数字 是否可以和牌
for(int i=1;i<10;i++)
{
if(arr[i]>0) //首先必须有剩余
{
vector<int> par=a;
par.push_back(i);
if(func(par))
{
cout<<i<<" ";
flag=true;
}
}
}
if(flag==false)
cout<<0<<endl;
}