zoukankan      html  css  js  c++  java
  • [ ZJOI 2006 ] Mahjong

    (\)

    (Description)


    现有权值分别为(1 ext~100)(100)种牌,分别给出每种排的张数(A_i),试判断能否胡牌,胡牌需要将所有牌不重不漏地分成以下几类:

    • 三张或四张相同的牌
    • 权值连续的三张牌
    • 两张相同的牌,这一类必须要有,而且只能有一个

    一组数据共需要(N)次判断,胡了输出“(Yes)”,否则输出"(No)"。

    • (Nin [1,100])(A_iin [0,100])

    (\)

    (Solution)


    没有写贪心,但是找到了两种(DP)的方式,按照便于理解的顺序介绍:

    (\)

    ( ext O(N imes 100^3))

    • 注意到每张牌只会影响到周围至多三种牌的出牌,而两两关系又可以互相导出,所以设(f[i][j][k][0/1])表示,当前处理到第(i)种牌,第(i-1)种牌出了(j)张,第(i)种牌出了(k)张,有(/)没有计算过对子,其余更靠前的牌全部出完是否可行,至于保证全部出完,后面再给证明。有显然的初始化(f[0][0][0][0]=1)

    • 对于对子有特殊转移:

       if(k>1) f[i][j][k][1]|=f[i][j][k-2][0];
      
    • 当前成立一个三张相同的牌,注意对子部分只能从相同的种类转移:

      if(k>2) {f[i][j][k][1]|=f[i][j][k-3][1];f[i][j][k][0]|=f[i][j][k-3][0];}
      
    • 当前成立一个四张相同的牌,讨论相同:

      if(k>3) {f[i][j][k][1]|=f[i][j][k-4][1];f[i][j][k][0]|=f[i][j][k-4][0];}
      
    • 注意到上面三种只对当前有要求,而成立三张连续的将会关系到前后的答案,因为确定后面的影响很不方便,我们不妨设三张连续的当前位置只考虑以当前的牌结束的部分。同样的,因为不便于考虑出多少组三张连续的牌,但注意到转移过程中会逐层转移自之前的情况,所以最优组合方案无需枚举,我们直接令当前的牌全部用于出连续三个一组的类型。同时为了保证除掉当前两种前面的全部都出完,所以向前找状态时默认第(i-2)种牌全部出完:

      if(j>=k&&a[i-2]>=k){
          f[i][j][k][1]|=f[i-1][a[i-2]-k][j-k][1];
          f[i][j][k][0]|=f[i-1][a[i-2]-k][j-k][0];
      }
      
    • 答案即为(f[100][A_{99}][A_{100}][1])是否为真。

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define N 1010
    #define R register
    #define gc getchar
    using namespace std;
    
    int a[N];
    bool f[N][N][N][2];
    
    inline int rd(){
      int x=0; bool f=0; char c=gc();
      while(!isdigit(c)){if(c=='-')f=1;c=gc();}
      while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
      return f?-x:x;
    }
    
    int main(){
      int t=rd();
      while(t--){
        for(R int i=1;i<=100;++i) a[i]=rd();
        memset(f,0,sizeof(f)); f[0][0][0][0]=1;
        for(R int i=1;i<=100;++i)
          for(R int j=0;j<=100;++j)
            for(R int k=0;k<=100;++k){
              if(k>1)f[i][j][k][1]|=f[i][j][k-2][0];
              if(k>2){f[i][j][k][1]|=f[i][j][k-3][1];f[i][j][k][0]|=f[i][j][k-3][0];}
              if(k>3){f[i][j][k][1]|=f[i][j][k-4][1];f[i][j][k][0]|=f[i][j][k-4][0];}
              if(j>=k&&a[i-2]>=k){
                f[i][j][k][1]|=f[i-1][a[i-2]-k][j-k][1];
                f[i][j][k][0]|=f[i-1][a[i-2]-k][j-k][0];
              }
            }
        puts(f[100][a[99]][a[100]][1]?"Yes":"No");
      }
      return 0;
    }
    

    (\)

    (Theta(N imes 100 imes 3^3))

    • 思路相同,但转化成了分别讨论相同的牌和连续的牌不同的出法。

    • 注意到其实两类之间是可以互相转换的,具体地说,对于连续三张的出法,一样的三张牌的组成超过三组就是没意义的了,因为它完全可以拆成三张牌独立出各自的一套,剩下的再组成三张连续出的方法,例如五组相同的组成((5 imes 3))就可以拆成三种牌各自出一套再加上两组((3 imes 3 imes 1+2 imes3)),或三种牌各自出一套四张一族的,再加上一组((3 imes 4 imes 1+1 imes 3))。我们发现,去掉所有的连续三张一组的部分以及一对的部分,如果剩下的每一类牌的数量都可以表示成(3x+4y)的形式,那么这个方案就是合法的。

    • 于是状态就能神奇的设计为(以下称相同的牌三个或四个一组的为一套,称连续三张一组的为顺),(f[i][0/1/2][0/1/2][0/1])表示当前处理到第(i)张牌,以第(i-1)张牌开始的顺出了(0/1/2)个,以第(i)张牌开始的顺出了(0/1/2)个,第(i-1)种和第(i)种其他的牌全部出成套,没出(/)出过对子的状态是否合法。同样有显然初始化(f[0][0][0][0]=1),注意预处理出所有能表示成形如(3x+4y)形式的数。

    • 转移考虑以第(i+1)个数为开始出了(s)个顺,注意要从合法的状态扩展,此时第(i+1)种牌剩余出成套的数即为(A_{i+1}-j-k-s),若该数字查表合法则转移。

    • 关于(0/1)状态间的转移,只需在每次枚举到(0)状态之后,讨论两种扩展即可。

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<cstdlib>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define N 110
    #define R register
    #define gc getchar
    using namespace std;
    
    int a[N]={0};
    bool val[100]={1},f[N][3][3][2];
    
    inline int rd(){
      int x=0; bool f=0; char c=gc();
      while(!isdigit(c)){if(c=='-')f=1;c=gc();}
      while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=gc();}
      return f?-x:x;
    }
    
    int main(){
      int t=rd();
      for(R int i=0;i<=35;++i)
        for(R int j=0;j<=25;++j)
          if(3*i+4*j<=100) val[3*i+4*j]=1;
      while(t--){
        memset(f,0,sizeof(f));
        f[0][0][0][0]=1;
        for(R int i=1;i<=100;++i) a[i]=rd();
        for(R int i=0;i<=99;++i)
          for(R int j=0;j<=2;++j)
            for(R int k=0;k<=2;++k){
              if(f[i][j][k][0])
                for(R int s=0;s<=2;++s){
                  int num=a[i+1]-j-k-s;
                  if(num>=0&&val[num]) f[i+1][k][s][0]=1;
                  if(num-2>=0&&val[num-2]) f[i+1][k][s][1]=1;
                }
              if(f[i][j][k][1])
                for(R int s=0;s<=2;++s){
                  int num=a[i+1]-j-k-s;
                  if(num>=0&&val[num])f[i+1][k][s][1]=1;
                }
            }
        puts(f[100][0][0][1]?"Yes":"No");
      }
      return 0;
    }
    
  • 相关阅读:
    [Vue warn]: Duplicate keys detected: '1'. This may cause an update error
    【转载】 github vue 高星项目
    前端面试题目汇总摘录(JS 基础篇)
    微信小程序-滚动Tab选项卡
    日期格式与标准时间之间互转
    git rebse 操作失误回退到上一步
    js判断数组中某个值是否存在
    git 不区分文件名大小写 解决办法
    React + antd 实现动态表单添加删除功能
    leetcode 重排链表
  • 原文地址:https://www.cnblogs.com/SGCollin/p/9585006.html
Copyright © 2011-2022 走看看