bzoj 1860: [Zjoi2006]Mahjong麻将 题解

【原题】

1860: [Zjoi2006]Mahjong麻将

Time Limit: 1 Sec   Memory Limit: 64 MB
Submit: 211   Solved: 122
[ Submit][ Status]

Description

Input

第一行一个整数N(N<=100),表示玩了N次超级麻将。 接下来N行,每行100个数a1..a100,描述每次玩牌手中各种牌的数量。ai表示数字为i的牌有ai张。(0<=ai<=100)

Output

输出N行,若胡了则输出Yes,否则输出No,注意区分Yes,No的大小写!

Sample Input

3
2 4 0 0 0 0 0 …… 0(一共98个0)
2 4 2 0 0 0 0 …… 0(一共97个0)
2 3 2 0 0 0 0 …… 0(一共97个0)

Sample Output

Yes
Yes
No

HINT

Source


【分析1】原来记得也有一道麻将的判和问题(当然不是国家集训队的那道)。当时随便用贪心使过去了。但是这里3张牌和4张牌结合在了一起,于是贪心肯定挂了。然后就百思不得其解。瞄了一眼题解:原来是爆搜。然后开始自己YY。因为最多是三张连续的牌,所以如果1--K-1张牌都没了,第K张牌一定只会影响到K+1和K+2。然后我就把K去爆搜。显然光是这样肯定T了。如果我把1--K-1以多种方法全部消掉了,第K种的状态就只需搜一遍即可。因此我根据K,K+1,K+2目前的数量以及K的位置HASH一下。

PS:用了凌神的64位自然溢出。

【代码1】

#include<cstdio>
#include<set>
using namespace std;
typedef unsigned long long ULL;
set<ULL>S;ULL base[101],a[101],mul=97;
inline ULL calc(int k) {return base[k]*a[k]+base[k+1]*a[k+1]+base[k+2]*a[k+2];}
int Test,i;
inline bool dfs(int k,bool two)
{
  ULL t=calc(k);
  if (S.find(t)!=S.end()) 
    return 0;
  S.insert(t);
  while (!a[k]) k++;
  if (k==101) return two;
  if (a[k]&&a[k+1]&&a[k+2])
  {
    a[k]--;a[k+1]--;a[k+2]--;
    if (dfs(k,two)) return 1;
    a[k]++;a[k+1]++;a[k+2]++;
  }
  if (a[k]>=4)
  {
    a[k]-=4;if (dfs(k,two)) return 1;a[k]+=4;
  }
  if (a[k]>=3)
  {
    a[k]-=3;if (dfs(k,two)) return 1;a[k]+=3;
  }
  if (a[k]>=2&&!two)
  {
    a[k]-=2;if (dfs(k,1)) return 1;a[k]+=2;
  }
  return 0;
}
int main()
{
  base[1]=1;for (i=2;i<=100;i++) base[i]=base[i-1]*mul;
  scanf("%d",&Test);
  while (Test--)
  {
    for (i=1;i<=100;i++) scanf("%lld",&a[i]);
    S.clear();
    if (dfs(1,0)) puts("Yes");else puts("No");
  }
  return 0;
}

【分析2】上述做法虽然样例过了,但是狂WA不止。后来发现这样的状态数太多了,HASH肯定会被卡掉。在WCY大神的指导下,我发现只需把K+1~N的情况哈希一下,这样子状态数显然变小了。

【代码】

#include<cstdio>
#include<set>
using namespace std;
typedef unsigned long long ULL;
set<ULL>S;ULL base[105],a[105],sum,mul=131ll;
int Test,i;
inline bool dfs(int k,bool two,ULL status)
{
  if (S.find(status)!=S.end()) return 0;
  S.insert(status);
  while (!a[k]&&k<=100) k++;
  if (k==101) return two;
  if (a[k]&&a[k+1]&&a[k+2]&&k<=98)
  {
    a[k]--;a[k+1]--;a[k+2]--;
    if (dfs(k,two,status-base[k]-base[k+1]-base[k+2])) return 1;
    a[k]++;a[k+1]++;a[k+2]++;
  }
  if (a[k]>=4)
  {
    a[k]-=4;if (dfs(k,two,status-base[k]*4)) return 1;a[k]+=4;
  }
  if (a[k]>=3)
  {
    a[k]-=3;if (dfs(k,two,status-base[k]*3)) return 1;a[k]+=3;
  }
  if (a[k]>=2&&!two)
  {
    a[k]-=2;if (dfs(k,1,status-base[k]*2-base[100])) return 1;a[k]+=2;
  }
  return 0;
}
int main()
{
  base[1]=1ll;for (i=2;i<=100;i++) base[i]=base[i-1]*mul;
  scanf("%d",&Test);
  while (Test--)
  {
    sum=0;for (i=1;i<=100;i++) scanf("%lld",&a[i]),sum+=base[i]*a[i];
    S.clear();
    if (dfs(1,0,sum)) puts("Yes");else puts("No");
  }
  return 0;
}

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值