题目链接

基本思路:最理想的方法是预处理处所有胡牌的状态的哈希值,然后对于每组输入,枚举每种新加入的牌,然后用哈希检验是否满足胡牌的条件。然而不幸的是,由于胡牌的状态数过多(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 (模拟+搜索+哈希+中途相遇)的更多相关文章

  1. HDU 4431 Mahjong 模拟

    http://acm.hdu.edu.cn/showproblem.php?pid=4431 不能说是水题了,具体实现还是很恶心的...几乎优化到哭但是DFS(还加了几个剪枝)还是不行...搜索一直T ...

  2. HDU 4431 Mahjong(模拟题)

    题目链接 写了俩小时+把....有一种情况写的时候漏了...代码还算清晰把,想了很久才开写的. #include <cstdio> #include <cstring> #in ...

  3. 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 ...

  4. HDU 4431 Mahjong (DFS,暴力枚举,剪枝)

    题意:给定 13 张麻将牌,问你是不是“听”牌,如果是输出“听”哪张. 析:这个题,很明显的暴力,就是在原来的基础上再放上一张牌,看看是不是能胡,想法很简单,也比较好实现,结果就是TLE,一直TLE, ...

  5. HDU - 5936: Difference(暴力:中途相遇法)

    Little Ruins is playing a number game, first he chooses two positive integers yy and KK and calculat ...

  6. HDU 5936 Difference 【中途相遇法】(2016年中国大学生程序设计竞赛(杭州))

    Difference Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total ...

  7. 【LOJ6254】最优卡组 堆(模拟搜索)

    [LOJ6254]最优卡组 题面 题解:常用的用堆模拟搜索套路(当然也可以二分).先将每个卡包里的卡从大到小排序,然后将所有卡包按(最大值-次大值)从小到大排序,并提前处理掉只有一张卡的卡包. 我们将 ...

  8. 【BZOJ4524】[Cqoi2016]伪光滑数 堆(模拟搜索)

    [BZOJ4524][Cqoi2016]伪光滑数 Description 若一个大于1的整数M的质因数分解有k项,其最大的质因子为Ak,并且满足Ak^K<=N,Ak<128,我们就称整数M ...

  9. 【BZOJ4345】[POI2016]Korale 堆(模拟搜索)

    [BZOJ4345][POI2016]Korale Description 有n个带标号的珠子,第i个珠子的价值为a[i].现在你可以选择若干个珠子组成项链(也可以一个都不选),项链的价值为所有珠子的 ...

随机推荐

  1. 神经网络的debug

    先建一个只有一层隐藏层的网络确定一切工作正常 在一个数据点上训练,training accuracy应该马上到100%而val accuracy等于随机猜测(overfit),如果不是说明有bug. ...

  2. TensorFlow实战第二课(添加神经层)

    莫烦tensorflow实战教学 1.添加神经层 #add_layer() import tensorflow as tf def add_layer(inputs,in_size,out_size, ...

  3. 【Python开发】python PIL读取图像转换为灰度图及另存为其它格式(也可批量改格式)

    例如有一幅图,文件名为"a.jpg'.  读取: from PIL import Image #或直接import Image im = Image.open('a.jpg') 将图片转换成 ...

  4. MHA简单部署

    MHA是目前比较成熟的mysql高可用集群方式之一. 一.参考文档:1.官方文档:[ https://github.com/yoshinorim/mha4mysql-manager/wiki ]2.个 ...

  5. hue的优化

    参考: 官网: https://docs.cloudera.com/documentation/enterprise/6/6.2/topics/hue_ref_arch.html 1/ 和开发沟通是否 ...

  6. liunx忘记用户密码

    1.vim /etc/my.cnf [mysqld] skip-grant-tables ##追加此行,跳过权限表, 2.重启mysql systemctl restart mysqld 3.mysq ...

  7. Arkady and a Nobody-men CodeForces - 860E (虚树)

    大意: 给定有根树, 根节点深度为$1$. 定义$r(a,b)$为$b$子树内深度不超过$a$的节点数$-1$ 定义$z_a$为$a$的所有祖先的$r$之和. 对于所有点求出$z$的值. 一个点$y$ ...

  8. Java 父类的static成员在子类中的继承情况

    父类的static成员在子类中的继承状况是怎么样的呢? 昨天突然想到这个问题,刚才试验了一下,发现很容易解释,没什么值得探讨的. 首先在学习继承时,书本都没有强调static成员有什么特殊的地方. 然 ...

  9. 北大 ACM highways问题研究(最小生成树)

    #include<stdlib.h> #include<stdio.h> #include<queue> struct vertex//代表一个村庄 { int m ...

  10. JavaScript的数组方法(array)

    数组方法: 1. concat()  合并数组 2. join()  将数组的元素拼接成字符串,并指定分隔符 3. push()  往数组末尾添加一个元素,并返回新的数组的长度 4. reverse( ...