HDU - 4431 Mahjong (模拟+搜索+哈希+中途相遇)
基本思路:最理想的方法是预处理处所有胡牌的状态的哈希值,然后对于每组输入,枚举每种新加入的牌,然后用哈希检验是否满足胡牌的条件。然而不幸的是,由于胡牌的状态数过多(4个眼+一对将),预处理的复杂度太高($O(34^5)$),因此需要想办法优化一下。
我们可以预处理出所有“加上一对将之后可以胡牌”的状态,这样预处理的复杂度就成了$O(34^4)$,在可接受的范围内了。在检验的时候,只需要枚举去掉哪一对将,就可以$O(1)$检验是否能胡牌了(有种中途相遇的感觉),另外两种特殊情况单独判断即可。
玄学优化方法:
1.在dfs和枚举检验的时候动态维护哈希值,而不是每次重复计算,这样可以节省很大一部分计算哈希值的时间。
2.dfs的时候,每一层的初始下标都不小于上一层,这样可以避免很多重复状态。
3.用哈希表代替set,可以大幅缩短存取哈希值的时间。
4.预处理处所有不少于2张的牌,这样就不用每次枚举的时候都从头开始找了。
综上,总复杂度约为$O(34^4+20000*34*7)$,应该接近极限了吧。
其实这道题也没这么复杂,直接枚举将暴力吃碰就行了,但为了锻炼自己搜索的玄学优化能力还是选择了扬长避短o( ̄▽ ̄)d
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int N=+,inf=0x3f3f3f3f,M=;
const char* s="mspc";
int id[],a[][N];
ll p[][N],pm[],h;
struct D {int x,y;};
vector<D> vec,vv;
struct Hashset {
static const int N=4e5,M=1e6+;
int hd[M],nxt[N],tot;
ll p[N];
void clear() {memset(hd,-,sizeof hd),tot=;}
void insert(ll x) {
int u=x%M;
for(int i=hd[u]; ~i; i=nxt[i])if(p[i]==x)return;
p[tot]=x,nxt[tot]=hd[u],hd[u]=tot++;
}
int count(ll x) {
int u=x%M;
for(int i=hd[u]; ~i; i=nxt[i])if(p[i]==x)return ;
return ;
}
} st;
void dfs(int dep,int x,int y) {
if(st.count(h))return;
if(dep==) {st.insert(h); return;}
for(int i=x; i<=; ++i)
for(int j=(i==x?y:); j<=(i==?:); ++j) {
if(a[i][j]<=) {
a[i][j]+=,h+=*p[i][j];
dfs(dep+,i,j);
a[i][j]-=,h-=*p[i][j];
}
if(j<=&&i!=&&a[i][j]<=&&a[i][j+]<=&&a[i][j+]<=) {
a[i][j]++,a[i][j+]++,a[i][j+]++,h+=p[i][j]+p[i][j+]+p[i][j+];
dfs(dep+,i,j);
a[i][j]--,a[i][j+]--,a[i][j+]--,h-=p[i][j]+p[i][j+]+p[i][j+];
}
}
}
bool Chii() {
for(int i=; i<=; ++i)
for(int j=; j<=(i==?:); ++j)if(a[i][j]&&a[i][j]!=)return ;
return ;
}
bool Kokushi() {
for(int i=; i<=; ++i)
for(int j=; j<=(i==?:); ++j) {
if((i!=&&(j==||j==))||(i==)) {if(!a[i][j])return ;}
else if(a[i][j])return ;
}
return ;
}
bool Hu() {
for(D t:vv)if(st.count(h-*p[t.x][t.y]))return ;
return ;
}
bool ok() {return Chii()||Kokushi()||Hu();}
int main() {
st.clear();
id['m']=,id['s']=,id['p']=,id['c']=;
pm[]=;
for(int i=; i<; ++i)pm[i]=pm[i-]*M;
for(int i=,k=; i<=; ++i)
for(int j=; j<=(i==?:); ++j,--k)p[i][j]=pm[k];
dfs(,,);
int T;
for(scanf("%d",&T); T--;) {
memset(a,,sizeof a),h=;
for(int i=; i<; ++i) {
int x;
char ch;
scanf("%d%c",&x,&ch);
a[id[ch]][x]++,h+=p[id[ch]][x];
}
vec.clear(),vv.clear();
for(int i=; i<=; ++i)
for(int j=; j<=(i==?:); ++j)if(a[i][j]>=)vv.push_back({i,j});
for(int x=; x<=; ++x)
for(int y=; y<=(x==?:); ++y)if(a[x][y]<=) {
a[x][y]++,h+=p[x][y];
if(a[x][y]==)vv.push_back({x,y});
if(ok())vec.push_back({x,y});
if(a[x][y]==)vv.pop_back();
a[x][y]--,h-=p[x][y];
}
if(vec.size()) {
printf("%d",vec.size());
for(D t:vec)printf(" %d%c",t.y,s[t.x]);
puts("");
} else puts("Nooten");
}
return ;
}
还有一种极限优化的方法,因为不同花色的牌是可以独立考虑的,因此单独判断出每种花色的牌是否合法(全为3或者111),如果能胡的话,则必然有三种花色合法,一种花色不合法,其中不合法的一组必然有一对将,枚举这对将,然后判断剩下的牌是否合法即可。
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int N=+,inf=0x3f3f3f3f,M=;
const char* s="mspc";
int id[],a[][N],c[N];
ll pm[],h[],hh;
struct D {int x,y;};
vector<D> vec;
struct Hashset {
static const int N=4e5,M=1e6+;
int hd[M],nxt[N],tot;
ll p[N];
void clear() {memset(hd,-,sizeof hd),tot=;}
void insert(ll x) {
int u=x%M;
for(int i=hd[u]; ~i; i=nxt[i])if(p[i]==x)return;
p[tot]=x,nxt[tot]=hd[u],hd[u]=tot++;
}
int count(ll x) {
int u=x%M;
for(int i=hd[u]; ~i; i=nxt[i])if(p[i]==x)return ;
return ;
}
} st1,st2;
void dfs1(int dep,int u) {
st1.insert(hh);
if(dep==)return;
for(int i=u; i<=; ++i) {
if(c[i]<=) {
c[i]+=,hh+=*pm[i];
dfs1(dep+,i);
c[i]-=,hh-=*pm[i];
}
if(c[i]<=&&c[i+]<=&&c[i+]<=) {
c[i]++,c[i+]++,c[i+]++,hh+=pm[i]+pm[i+]+pm[i+];
dfs1(dep+,i);
c[i]--,c[i+]--,c[i+]--,hh-=pm[i]+pm[i+]+pm[i+];
}
}
}
void dfs2(int dep,int u) {
st2.insert(hh);
if(dep==)return;
for(int i=u; i<=; ++i)if(c[i]<=) {
c[i]+=,hh+=*pm[i];
dfs2(dep+,i);
c[i]-=,hh-=*pm[i];
}
}
bool Chii() {
for(int i=; i<=; ++i)
for(int j=; j<=(i==?:); ++j)if(a[i][j]&&a[i][j]!=)return ;
return ;
}
bool Kokushi() {
for(int i=; i<=; ++i)
for(int j=; j<=(i==?:); ++j) {
if((i!=&&(j==||j==))||(i==)) {if(!a[i][j])return ;}
else if(a[i][j])return ;
}
return ;
}
bool Hu() {
int x=-;
for(int i=; i<; ++i)if(!st1.count(h[i])) {
if(~x)return ;
x=i;
}
if(!st2.count(h[])) {
if(~x)return ;
x=;
}
if(x!=) {for(int i=; i<=; ++i)if(a[x][i]>=&&st1.count(h[x]-*pm[i]))return ;}
else {for(int i=; i<=; ++i)if(a[x][i]>=&&st2.count(h[]-*pm[i]))return ;}
return ;
}
bool ok() {return Chii()||Kokushi()||Hu();}
int main() {
st1.clear(),st2.clear();
pm[]=;
for(int i=; i<; ++i)pm[i]=pm[i-]*M;
id['m']=,id['s']=,id['p']=,id['c']=;
dfs1(,),dfs2(,);
int T;
for(scanf("%d",&T); T--;) {
memset(a,,sizeof a);
memset(h,,sizeof h);
for(int i=; i<; ++i) {
int x;
char ch;
scanf("%d%c",&x,&ch);
a[id[ch]][x]++,h[id[ch]]+=pm[x];
}
vec.clear();
for(int x=; x<=; ++x)
for(int y=; y<=(x==?:); ++y)if(a[x][y]<=) {
a[x][y]++,h[x]+=pm[y];
if(ok())vec.push_back({x,y});
a[x][y]--,h[x]-=pm[y];
}
if(vec.size()) {
printf("%d",vec.size());
for(D t:vec)printf(" %d%c",t.y,s[t.x]);
puts("");
} else puts("Nooten");
}
return ;
}
HDU - 4431 Mahjong (模拟+搜索+哈希+中途相遇)的更多相关文章
- HDU 4431 Mahjong 模拟
http://acm.hdu.edu.cn/showproblem.php?pid=4431 不能说是水题了,具体实现还是很恶心的...几乎优化到哭但是DFS(还加了几个剪枝)还是不行...搜索一直T ...
- HDU 4431 Mahjong(模拟题)
题目链接 写了俩小时+把....有一种情况写的时候漏了...代码还算清晰把,想了很久才开写的. #include <cstdio> #include <cstring> #in ...
- HDU 4431 Mahjong(枚举+模拟)(2012 Asia Tianjin Regional Contest)
Problem Description Japanese Mahjong is a four-player game. The game needs four people to sit around ...
- HDU 4431 Mahjong (DFS,暴力枚举,剪枝)
题意:给定 13 张麻将牌,问你是不是“听”牌,如果是输出“听”哪张. 析:这个题,很明显的暴力,就是在原来的基础上再放上一张牌,看看是不是能胡,想法很简单,也比较好实现,结果就是TLE,一直TLE, ...
- HDU - 5936: Difference(暴力:中途相遇法)
Little Ruins is playing a number game, first he chooses two positive integers yy and KK and calculat ...
- HDU 5936 Difference 【中途相遇法】(2016年中国大学生程序设计竞赛(杭州))
Difference Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total ...
- 【LOJ6254】最优卡组 堆(模拟搜索)
[LOJ6254]最优卡组 题面 题解:常用的用堆模拟搜索套路(当然也可以二分).先将每个卡包里的卡从大到小排序,然后将所有卡包按(最大值-次大值)从小到大排序,并提前处理掉只有一张卡的卡包. 我们将 ...
- 【BZOJ4524】[Cqoi2016]伪光滑数 堆(模拟搜索)
[BZOJ4524][Cqoi2016]伪光滑数 Description 若一个大于1的整数M的质因数分解有k项,其最大的质因子为Ak,并且满足Ak^K<=N,Ak<128,我们就称整数M ...
- 【BZOJ4345】[POI2016]Korale 堆(模拟搜索)
[BZOJ4345][POI2016]Korale Description 有n个带标号的珠子,第i个珠子的价值为a[i].现在你可以选择若干个珠子组成项链(也可以一个都不选),项链的价值为所有珠子的 ...
随机推荐
- go使用go-redis操作redis 连接类型,pipline, 发布订阅
内容: 一 . 客户端Client(普通模式,主从模式,哨兵模式)二. conn连接(连接, pipline, 发布订阅等)三. 示例程序(连接, pipline, 发布订阅等)客户端Client 普 ...
- HTTPS SSL TLS 相关理解
1,在理解 HTTPS SSL TLS 之前先对常用的加密方式进行一个简述: (1),对称加密: 采用一个密钥,对明文进行加密生成密文,相反采用此密钥可对加密后的密文进行解密还原成明文. 代表算法有, ...
- Java入门第一季学习总结
一.课程总概 这门课程的学习难度属于入门级别,又由于有c++的基础,所以学习这门课程也是比较轻松的.可以简单地把这门课的学习分为四部分:第一部分,java的介绍(第一章):第二部分,java的数据类型 ...
- 【DSP开发】C6678的中断控制器
分两层,一层是每个core内部的中断控制器,这个叫interrupt controller,简写intc:一层是整个芯片的,属于芯片级的,在每个core的外面,这个叫chip-level interr ...
- 建立EF访问数据库架构时,出现One or more validation errors were detected during model generation
原因是因为我在写实体类的时候没有为实体类中的属性声明一个主键,即用[key]特性标注在属性上,这样DbContext才能为我们在数据库上找到对应的主键 using System.ComponentMo ...
- PTA(Advanced Level)1036.Boys vs Girls
This time you are asked to tell the difference between the lowest grade of all the male students and ...
- 卸载pkg安装包
1 基本原理 1.1 查看某个pkg安装包安装的所有文件 第一,查看所有安装的pkg $ pkgutil --pkgs 第二,查看指定pkg的所有安装文件 $ pkgutil --files the- ...
- NLP自然语言处理中英文分词工具集锦与基本使用介绍
一.中文分词工具 (1)Jieba (2)snowNLP分词工具 (3)thulac分词工具 (4)pynlpir 分词工具 (5)StanfordCoreNLP分词工具 1.from stanfor ...
- 从零开始,SpreadJS 新人学习笔记
Hello,大家好,我是Fiona,从事前端开发工作,我十分热爱我的工作和一直默默栽培我的老板(这段请加粗). 前不久,接到老板的安排: 说实话,接到这个需求,我整个人的状态是这样的: 但是,我不能辜 ...
- MyBatis 安装和配置
在这里我们使用 MyBatis 开发一个简单的 Java 项目(默认你已安装JDK和MySQL及会使用Maven的基本操作),可以与上一篇通过底层操作数据进行比较 1.新建表 students,插入数 ...