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].现在你可以选择若干个珠子组成项链(也可以一个都不选),项链的价值为所有珠子的 ...
随机推荐
- Pycharm从虚拟环境切换到本地环境
切换到本地: 点击左上角File – settings , 在打开的对话框中选择:Project xxx (xxx是你项目的名称) – Project Interpreter ,在右边可以看到解释器, ...
- pytorch中的激励函数(详细版)
初学神经网络和pytorch,这里参考大佬资料来总结一下有哪些激活函数和损失函数(pytorch表示) 首先pytorch初始化: import torch import t ...
- Spring体系
1.Spring简介 Spring是一个轻量级Java开发框架,最早有Rod Johnson创建,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题.它是一个分层的JavaSE/JavaEE ...
- 前端实现app引导页面动画效果
插件描述:jQuery引导插件TourTip 交互式可视化指南网页上的元素.使用方法 步骤1: 将以下标记添加到您的文档的<head> 你还需要复制旁边插件的css文件夹和下载的IMG文件 ...
- SpringMVC必备知识点汇总
1.参数接收 1.1 数组 1.2 文件上传 1.2.1 单个文件上传 1.2.2 多个文件上传 1.2.3 文件上传时,携带其他数据 2.参数校验 参数校验:https://www.cnblogs. ...
- POP与OOP编程模式对比
面向过程:(procedure oriented programming 即:POP) 代表:C/C++ 向过程程序设计,它是以功能为中心来进行思考和组织的一种编程方式,强调的是系统的数据被加工和处理 ...
- python之cookies
#cookies保存在文档头的内部,将cookies信息保存在文档中 userinfo={'} r=requests.get('http://httpbin.org/get',cookies=user ...
- # 匈牙利算法(二分图最大匹配)- hdu 过山车
匈牙利算法(二分图最大匹配)- hdu 过山车 Hdu 2063 二分图:图中的点可以分成两组U,V,所有边都是连接U,V中的顶点.等价定义是:含奇数条边的图. 匹配:一个匹配是一个边的集合,其中任意 ...
- spark教程(二)-shell操作
spark 支持 shell 操作 shell 主要用于调试,所以简单介绍用法即可 支持多种语言的 shell 包括 scala shell.python shell.R shell.SQL shel ...
- Gsview裁剪EPS文件
(1)菜单栏 “options--show bounding boxs” 选中. (2)打开eps图,然后File->PS TO EPS,不选择Automatically calculate ...