洛谷P5279 [ZJOI2019]麻将

https://www.luogu.org/problemnew/show/P5279

以下为个人笔记,建议别看:

首先考虑如何判一个牌型是否含有胡的子集。先将牌型表示为一个数组num,其中num[i]表示牌i出现了几张。

先判七对子(略)。

然后做一个dp。(后面的算法不支持"在最后(i接近n时)进行特判的dp",如果"在开始(i为1,2,3时)进行特判"也可能难以实现,因此可能需要改进一下dp。)

ans[i][j][k][l]表示考虑前i种花色的牌,是否预留了对子(j为1有,j为0无),顺子(i-1,i,i+1)取k个,顺子(i,i+1,i+2)取l个,把剩余的第1~i种的牌都尽量组成刻子,最多能得到多少个面子(这些顺子自身的贡献要等取到最大的那个数时再算,可以避免一些边界处理,比如不会出现(1,0,-1),(n+1,n,n-1)之类的顺子)。由于3个相同的顺子等同于3个刻子,只需要考虑0<=k<=2,0<=l<=2即可。当且仅当ans[n][1][0..2][0..2]的最大值>=4时牌可以胡。(转移略)

可以得到这样一个暴力

  1 #include<cstdio>
  2 #include<algorithm>
  3 #include<cstring>
  4 using namespace std;
  5 #define fi first
  6 #define se second
  7 #define pb push_back
  8 typedef long long ll;
  9 typedef unsigned long long ull;
 10 
 11 inline void setmax(int &a,int b) 
 12 {
 13     if(a<b)    a=b;
 14 }
 15 
 16 int num[111],n;
 17 inline char judge()
 18 {
 19     int t1=0,i,k,l,tt,ed;
 20     for(i=1;i<=n;++i)
 21         if(num[i]>=2)
 22             ++t1;
 23     if(t1>=7)    return 1;
 24     static int ok[115][2][3][3];
 25     memset(ok,192,sizeof(ok));//(i-1,i,i+1)->k,(i,i+1,i+2)->l
 26     ok[0][0][0][0]=0;
 27     for(i=0;i<n;++i)
 28     {
 29         for(k=0;k<=2;++k)
 30         {
 31             for(l=0;l<=2;++l)
 32             {
 33                 ed=min(2,num[i+1]-k-l);
 34                 for(tt=0;tt<=ed;++tt)
 35                 {
 36                     setmax(ok[i+1][0][l][tt],ok[i][0][k][l]+k+(num[i+1]-k-l-tt)/3);
 37                     setmax(ok[i+1][1][l][tt],ok[i][1][k][l]+k+(num[i+1]-k-l-tt)/3);
 38                 }
 39                 ed=min(2,num[i+1]-k-l-2);
 40                 for(tt=0;tt<=ed;++tt)
 41                 {
 42                     setmax(ok[i+1][1][l][tt],ok[i][0][k][l]+k+(num[i+1]-k-l-tt-2)/3);
 43                 }
 44             }
 45         }
 46     }
 47     int ans=0;
 48     for(k=0;k<=2;++k)
 49         for(l=0;l<=2;++l)
 50             setmax(ans,ok[n][1][k][l]);
 51     return ans>=4;
 52 }
 53 const int md=998244353;
 54 #define addto(a,b) ((a)+=(b),((a)>=md)&&((a)-=md))
 55 int fac[511],ifac[511];
 56 int ans;
 57 /*
 58 ull ttttt[555];
 59 void out()
 60 {
 61             for(int i=1;i<=200;++i)
 62                 printf("%llu ",ttttt[i]);
 63             puts("");
 64             int t;
 65             scanf("%d",&t);
 66 }
 67 */
 68 void dfs(int p,int ddd)
 69 {
 70     if(judge())
 71     {
 72         addto(ans,ull(p)*ddd%md*fac[4*n-13-p]%md);
 73         /*
 74         ttttt[p]+=ddd;++ttttt[0];
 75         if(ttttt[0]%10000==0)
 76         {
 77             out();
 78         }
 79         */
 80         //if(p<10)
 81         //printf("%d\n",p);
 82         return;
 83     } 
 84     for(int i=1;i<=n;++i)
 85         if(num[i]<4)
 86         {
 87             ++num[i];
 88             dfs(p+1,ull(ddd)*(5-num[i])%md);
 89             --num[i];
 90         }
 91 }
 92 int poww(int a,int b)
 93 {
 94     int ans=1;
 95     for(;b;b>>=1,a=ull(a)*a%md)
 96         if(b&1)
 97             ans=ull(ans)*a%md;
 98     return ans;
 99 }
100 int main()
101 {
102     /*
103     int i,w,t;
104     scanf("%d%d",&n,&t);
105     for(i=1;i<=t;++i)
106     {
107         scanf("%d",&w);
108         ++num[w];
109     }
110     printf("%d\n",int(judge()));
111     */
112     int i,w,t;
113     fac[0]=1;
114     for(i=1;i<=500;++i)
115         fac[i]=ull(fac[i-1])*i%md;
116     ifac[500]=poww(fac[500],md-2);
117     for(i=500;i>=1;--i)
118         ifac[i-1]=ull(ifac[i])*i%md;
119     scanf("%d",&n);
120     for(i=1;i<=13;++i)
121     {
122         scanf("%d%d",&w,&t);
123         ++num[w];
124     }
125     dfs(0,1);
126     printf("%llu\n",ull(ans)*ifac[4*n-13]%md);
127     //out();
128     return 0;
129 }
View Code

可以根据这个dp建成一个类似自动机的东西。自动机上的状态(点)可以当做一个三维数组再加上一个数字,三维就是就是ans的后三维,数组的元素就是在那三维的条件下最多凑出的面子数,加上的数字是"有多少种数字的牌可以凑出对子"(为了把七对子的统计放进自动机)。转移边就是根据dp的转移来连。对于起始状态,显然额外数字为0,设数组为a,则数组中只有a[0][0][0]=0,其余全为-inf。可以用和开头一样的方法判断一个状态是否是胡牌状态。为了方便,可以把所有胡牌的状态合并成一个状态,它的所有转移边都指向自身。

爆搜一下,可以发现这个自动机的状态并不是很多(不知道为什么)。爆搜的方法就是搞一个bfs,队列中一开始只有初始状态,每次从队列首部取出一个状态,枚举下一个数牌数量是0/1/2/3/4进行转移,得到它的后继状态。如果后继状态胡了:直接向某一个钦点的结束状态连边连边。如果后继状态没有胡:如果没有遍历过后继状态就建立后继状态对应的点并连边,然后将后继状态加入队列,否则直接向后继状态连边。判断后继状态是否遍历过可以强行搞一个map之类的。为了复杂度对,需要让数组中各个值对4取min,额外数字对7取min(这一步的确是有必要的,因为可能a[0]里面有很大的数字,但是a[1]里面都很小,虽然有很大数字,仍然不能胡牌,导致有无限个状态)

如何根据这个自动机计算答案?(以下“能胡”指存在一个子集胡牌)

最终的答案=所有方案胡牌巡数的平均值=所有方案胡牌巡数总和/方案数

此处的一种方案:给剩余的未摸进来的牌每张一个(a,b)的编号,表示数字为a的第b张牌;对于这些(a,b)对的任意一个排列就是一种方案。

先算所有方案胡牌巡数总和。把每个方案拆开,拆成没胡牌前每一巡1的贡献,胡牌那一巡1的贡献,两者分开考虑。对于两个部分,都将所有方案一起考虑。对于第一部分,每一巡分开考虑,相当于每一巡的贡献是这一巡有多少方案不能胡。对于第二部分,由于所有牌摸进来必定能胡,贡献就是方案数。

胡牌巡数总和除以方案数,得到答案=$\frac{\sum_{i=1}^{4n-13}额外摸i张牌不能胡的方案数}{总方案数}+1$

怎么算这个东西?首先,总方案数等于$(4n-13)!$

dp一下,ans[i][j][k]表示考虑前i种牌,额外摸了j张,当前在自动机上状态是k的方案数(考虑最终答案,额外摸i张牌不能胡的方案,相当于先从所有(a,b)对中选出i个,让它们作为前i张摸上来的牌,如果它们不能胡,则产生贡献i!(4n-13-i)!;因此此处的一种方案定义为从前i种牌产生的所有(a,b)对中选出j个,最后统计答案时ans[n][j][k]只有当k!=T时才产生贡献,对答案的贡献要乘上j!(4n-13-j)!)(转移略)

  1 #include<cstdio>
  2 #include<algorithm>
  3 #include<cstring>
  4 #include<vector>
  5 #include<map>
  6 using namespace std;
  7 #define fi first
  8 #define se second
  9 #define mp make_pair
 10 #define pb push_back
 11 typedef long long ll;
 12 typedef unsigned long long ull;
 13 const int md=998244353;
 14 #define addto(a,b) ((a)+=(b),((a)>=md)&&((a)-=md))
 15 inline void setmax(int &a,int b)
 16 {
 17     if(a<b && b>=0)    a=b;
 18 }
 19 inline void setmin(int &a,int b)
 20 {
 21     if(a>b)    a=b;
 22 }
 23 struct st
 24 {
 25     int a[2][3][3],b;
 26 };
 27 inline bool operator<(const st &a,const st &b)
 28 {
 29     /*
 30     int t=memcmp(a.a,b.a,sizeof(a.a));
 31     if(!t)    return a.b<b.b;
 32     else    return t<0;
 33     */
 34     for(int i=0;i<=1;++i)
 35         for(int j=0;j<=2;++j)
 36             for(int k=0;k<=2;++k)
 37                 if(a.a[i][j][k]!=b.a[i][j][k])
 38                     return a.a[i][j][k]<b.a[i][j][k];
 39     return a.b<b.b;
 40 }
 41     inline bool judge(const st &a)
 42     {
 43         if(a.b>=7)    return 1;
 44         int j,k;
 45         for(j=0;j<3;++j)
 46             for(k=0;k<3;++k)
 47                 if(a.a[1][j][k]>=4)
 48                     return 1;
 49         return 0;
 50     }
 51     inline void nxt_state(const st &a,st &b,int x)
 52     {
 53         b.b=min(7,a.b+(x>=2));
 54         memset(b.a,192,sizeof(b.a));
 55         int i,j,k;
 56         for(i=0;i<=2;++i)
 57             for(j=0;j<=2;++j)
 58             {
 59                 for(k=0;k<=min(2,x-i-j);++k)
 60                 {
 61                     setmax(b.a[0][j][k],a.a[0][i][j]+i+(x-i-j-k)/3);
 62                     setmax(b.a[1][j][k],a.a[1][i][j]+i+(x-i-j-k)/3);
 63                 }
 64                 for(k=0;k<=min(2,x-i-j-2);++k)
 65                 {
 66                     setmax(b.a[1][j][k],a.a[0][i][j]+i+(x-i-j-k-2)/3);
 67             }
 68         }
 69         for(i=0;i<=2;++i)
 70             for(j=0;j<=2;++j)
 71             {
 72                 setmin(b.a[0][j][k],4);
 73                 setmin(b.a[1][j][k],4);
 74             }
 75 
 76 }
 77 map<st,int> ma;
 78 int trans[4011][5];
 79 /*
 80 struct E
 81 {
 82     int to,nxt;
 83 }e[200011];
 84 int f1[10011],ne;
 85 inline void me(int x,int y)
 86 {
 87     e[++ne].to=y;e[ne].nxt=f1[x];f1[x]=ne;
 88 }
 89 */
 90 int mem,S,T;st ta[4011];
 91 /*
 92 void out()
 93 {
 94     printf("%d %d\n",S,T);
 95     for(int i=1;i<=mem;++i)
 96     {
 97         printf("id%d\n",i);
 98         for(int j=0;j<=1;++j)
 99         {
100             for(int k=0;k<=2;++k)
101             {
102                 for(int l=0;l<=2;++l)
103                 {
104                     printf("%d ",ta[i].a[j][k][l]);
105                 }
106                 puts("");
107             }
108             puts("/////////////////////");
109         }
110         for(int j=0;j<=4;++j)
111             printf("%d ",trans[i][j]);
112         puts("");
113         printf("%d\n---------------------\n",ta[i].b);
114     }
115 }
116 */
117 void init()
118 {
119     st t1,t2;int t,i;
120     T=++mem;
121     S=++mem;
122     memset(t1.a,192,sizeof(t1.a));
123     t1.b=0;
124     t1.a[0][0][0]=0;
125     ma[t1]=S;ta[S]=t1;
126     for(t=S;t<=mem;++t)
127     {
128         t1=ta[t];
129         for(i=0;i<=4;++i)
130         {
131             nxt_state(t1,t2,i);
132             if(judge(t2))
133                 trans[t][i]=T;
134             else if(!ma.count(t2))
135             {
136                 ma[t2]=++mem;
137                 ta[mem]=t2;
138                 trans[t][i]=mem;
139             }
140             else
141                 trans[t][i]=ma[t2];
142         }
143     }
144     for(i=0;i<=4;++i)
145         trans[T][i]=T;
146 }
147 
148 int n1[101],n,ans;
149 int an1[101][389][2101];
150 int fac[10011],ifac[10011];
151 int C(int n,int m)    {return ull(fac[n])*ifac[m]%md*ifac[n-m]%md;}
152 int CC[6][6];
153 int main()
154 {
155     int i,t1,t2,j,k,l;
156     fac[0]=1;
157     for(i=1;i<=10000;++i)
158         fac[i]=ull(fac[i-1])*i%md;
159     //printf("1t%d\n",fac[10000]);
160     ifac[10000]=265002293;
161     for(i=10000;i>=1;--i)
162         ifac[i-1]=ull(ifac[i])*i%md;
163     //printf("2t%d\n",ifac[1]);
164     init();
165     for(i=0;i<=5;++i)
166         for(j=0;j<=i;++j)
167             CC[i][j]=C(i,j);
168     /*
169     printf("1t%d\n",mem);
170     for(i=21;i<=25;++i)
171     {
172         for(int j=0;j<=4;++j)
173             printf("%d ",trans[i][j]);
174         puts("");
175         for(int j=0;j<=2;++j)
176         {
177             for(int k=0;k<=2;++k)
178                 printf("%d ",ta[i].a[0][j][k]);
179             puts("");
180         }
181         puts("");
182         for(int j=0;j<=2;++j)
183         {
184             for(int k=0;k<=2;++k)
185                 printf("%d ",ta[i].a[1][j][k]);
186             puts("");
187         }
188         puts("");
189     }
190     return 0;
191     */
192     //printf("1t%d\n",mem);
193     scanf("%d",&n);
194     for(i=1;i<=13;++i)
195     {
196         scanf("%d%d",&t1,&t2);
197         ++n1[t1];
198     }
199     an1[0][0][S]=1;
200     for(i=0;i<n;++i)
201     {
202         for(j=0;j<=4*n-13;++j)
203         {
204             for(k=1;k<=mem;++k)
205             {
206                 for(l=0;l<=4-n1[i+1];++l)
207                 {
208                     addto(an1[i+1][j+l][trans[k][l+n1[i+1]]],ull(an1[i][j][k])*CC[4-n1[i+1]][l]%md);
209                     //预处理C(a,b)减小常数
210                 }
211             }
212         }
213     }
214     for(j=1;j<=4*n-13;++j)
215     {
216         for(k=1;k<=mem;++k)
217             if(k!=T)
218             {
219                 addto(ans,ull(an1[n][j][k])*fac[j]%md*fac[4*n-13-j]%md);
220             }
221     }
222     printf("%llu\n",(ull(ans)*ifac[4*n-13]+1)%md);
223     return 0;
224 }
View Code

 

posted @ 2019-04-22 10:51  hehe_54321  阅读(617)  评论(0编辑  收藏  举报
AmazingCounters.com