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].现在你可以选择若干个珠子组成项链(也可以一个都不选),项链的价值为所有珠子的 ...
随机推荐
- SparkCore的性能优化
1.广播变量 1.1. Spark提供的Broadcast Variable,是只读的,并且在每个节点上只会有一份副本,而不会为每个task都拷贝一份副本 1.2.它的最大作用,就是减少变量到各个节点 ...
- <转>经典测试用例:电梯、杯子、桌子、洗衣机
1.测试项目:电梯 需求测试:查看电梯使用说明书.安全说明书等 界面测试:查看电梯外观 功能测试:测试电梯能否实现正常的上升和下降功能.电梯的按钮是否都可以用: 电梯门的打开,关闭是否正常:报警装置是 ...
- Tensorflow实战第十一课(RNN Regression 回归例子 )
本节我们会使用RNN来进行回归训练(Regression),会继续使用自己创建的sin曲线预测一条cos曲线. 首先我们需要先确定RNN的各种参数: import tensorflow as tf i ...
- 【DSP开发】【VS开发】YUV与RGB格式转换
[视频处理]YUV与RGB格式转换 YUV格式具有亮度信息和色彩信息分离的特点,但大多数图像处理操作都是基于RGB格式. 因此当要对图像进行后期处理显示时,需要把YUV格式转换成RGB格式. RGB与 ...
- 【Linux开发】linux设备驱动归纳总结(六):1.中断的实现
linux设备驱动归纳总结(六):1.中断的实现 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- luoguP2015(简单树形DP)
题目链接:https://www.luogu.org/problemnew/show/P2015 题意:给定一颗结点个数为n的树,有n-1条边,每条边有个权值,树根为1.现在给出q <=n,问剪 ...
- Python学习【day04】- Python基础(集合、函数)
集合 #!/usr/bin/env python # -*- coding:utf8 -*- # set集合 只可放不可变的数据类型,本身是可变数据类型,无序 # s = {1,2,3,[1,2,3] ...
- 记java的那些编辑器的故事之凌嘉文+李晓彤-结对编程
[写在前面]这次是复用个人项目进行结对编程,其实主要复用的就是凌老板的出题部分和我的文件读写部分,其余部分都是新学的.在这次编程中也涨了很多知识,其中最最最让人哭笑不得的就是:两个人用了不一样的编辑器 ...
- POJ - 3469 Dual Core CPU (最小割)
(点击此处查看原题) 题意介绍 在一个由核A和核B组成的双核CPU上执行N个任务,任务i在核A上执行,花费Ai,在核B上执行,花费为Bi,而某两个任务之间可能需要进数据交互,如果两个任务在同一个核上执 ...
- 移除django的旧版本
移除django的旧版本 下面这一段代码打进去绝对能看到你想要的,根据这个路径去找版本文件夹,他的名字应该是django.2xx.xxx很长一段,请你删了它! import django import ...