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 ",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 ",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 ",ull(ans)*ifac[4*n-13]%md); 127 //out(); 128 return 0; 129 }
可以根据这个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 ",S,T); 95 for(int i=1;i<=mem;++i) 96 { 97 printf("id%d ",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 --------------------- ",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 ",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 ",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 ",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 ",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 ",(ull(ans)*ifac[4*n-13]+1)%md); 223 return 0; 224 }