[GXOI/GZOI2019]宝牌一大堆
发布日期:2021-05-07 01:01:19 浏览次数:27 分类:精选文章

本文共 5233 字,大约阅读时间需要 17 分钟。

题目

题目描述

将一种麻将牌(这里假设大家都会打)的牌,定义一个权值:有多少种方案凑出这幅牌 × \times ×方案的优秀度。优秀度由两个因素决定:牌型;宝牌。每张宝牌将会让优秀度翻倍,也就是 2 x 2^x 2x倍。牌型带来的特殊优秀度见下方。

打麻将你得知道的事情:和

——有哪些牌?

  • 普通的:万子、筒子、条子。
  • 新鲜的:东南西北;红中、白板、绿发。

——牌的组合?

  • 刻子:三张一样的牌。
  • 顺子:三张连续的牌。顺子、刻子统称为“面子”。
  • 对子:两张一样的牌。在和牌组合中也叫做“将”。
  • 杠子:四张一样的牌。

——和牌的组合?

  • 过小份: 4 × 4\times 4×面子 + 1 × +1\times +1×将。共 14 14 14张。
  • 七对: 7 × 7\times 7×对子。共 14 14 14张。对子是不同的。七对的优秀度会额外扩大为原来的 7 7 7
  • 国士无双:仅包含数字为一或九的筒、万、条,加上东南西北中白发,每种至少一张。共 14 14 14张。优秀度扩大为原来的 13 13 13
  • 杠一次,剩下的组成 3 × 3\times 3×面子 + 1 × +1\times +1×将。共 15 15 15张。
  • 杠两次,剩下的组成 2 × 2\times 2×面子 + 1 × +1\times +1×将。共 16 16 16张。
  • 杠三次,剩下的组成 1 × 1\times 1×面子 + 1 × +1\times +1×将。共 17 17 17张。
  • 杠四次,剩下的组成 1 × 1\times 1×将。共 18 18 18张。

输入输出格式

略。

数据范围与约定

对于 100 % 100\% 100%的数据,数据组数 T ≤ 2500 T\le 2500 T2500,宝牌的数量不超过 20 20 20张。数据保证合法。

思路

龟速的 DP \text{DP} DP

d p [ i ] [ j ] [ k ] [ l ] [ r ] dp[i][j][k][l][r] dp[i][j][k][l][r]表示有 i i i个杠、 j j j个对、 k k k个面子,最大优秀度。倒数第二个选了 l l l个;倒数第一个选了 r r r个。并且都“空闲”着——没有被统计为面子什么的。显然有 i ≤ 4 , j ≤ 7 , k ≤ 4 , l ≤ 4 , r ≤ 4 i\le 4,j\le 7,k\le 4,l\le 4,r\le 4 i4,j7,k4,l4,r4.

国士无双单独考虑。

然后我就 TLE \text{TLE} TLE飞了……在某谷上只能过 20 pts 20\text{pts} 20pts的数据。

高效的 DP \text{DP} DP

我们有很多优化可以打——

  • 七对单独做。发现 j j j之所以要开到 7 7 7,全是七对在搞鬼。如果我模仿国士无双的处理方法,我就可以把 j j j开成 0 / 1 0/1 0/1.
  • 杠子不需要。发现杠子的贡献无非是 2 C 4 4 = 2 2 C_{4}^{4}=2 2C44=2,而刻子的贡献是 C 4 3 = 4 C_{4}^{3} = 4 C43=4,所以根本不会用杠子的。也就是说,其实只有七对国士无双四面一将需要被考虑。于是我们又降了一维。
  • 如果你用 d p [ i , j , k , l , r ] dp[i,j,k,l,r] dp[i,j,k,l,r]去更新别人,那么 d p [ i , j , k , l , r ] = 0 dp[i,j,k,l,r]=0 dp[i,j,k,l,r]=0时就不用更新了。

这就是全部优化了。但这些就足以助你通过此题!

代码

#include 
#include
#include
#include
#include
#include
using namespace std;inline int readint(){ int a = 0; char c = getchar(), f = 1; for(; c<'0' or c>'9'; c=getchar()) if(c == '-') f = -f; for(; '0'<=c and c<='9'; c=getchar()) a = (a<<3)+(a<<1)+(c^48); return a*f;}inline void writeint(long long x){ if(x < 0) putchar('-'), x = -x; if(x > 9) writeint(x/10); putchar(x%10+'0');}# define FOR(i,a,b) for(register int i=(a); i<=(b); ++i)# define REP(i,a,b) for(register int i=(a); i>=(b); --i)inline void getMin(long long &p,long long q){ if(p > q) p = q; /* I passed by ... */}inline void getMax(long long &p,long long q){ if(p < q) p = q; /* ... your whole world */}string intAndChar(int x,char c){ string s = ""; s.push_back(char(x+'0')); s.push_back(c); return s;}map
dic; /* dictionary */bool gsws[50]; /* 国士无双 */void prepare(){ for(int i=1; i<=9; ++i) dic[intAndChar(i,'m')] = i; for(int i=1; i<=9; ++i) dic[intAndChar(i,'p')] = i+9; for(int i=1; i<=9; ++i) dic[intAndChar(i,'s')] = i+18; for(int i=0; i<=18; i+=9) gsws[1+i] = gsws[9+i] = true; dic["E"] = 28, dic["S"] = 29; dic["W"] = 30, dic["N"] = 31; dic["Z"] = 32, dic["B"] = 33, dic["F"] = 34; for(int i=28; i<=34; ++i) gsws[i] = true;}int cnt[50]; bool special[50]; char __s[10];void input(){ for(int i=1; i<=34; ++i) cnt[i] = 0, special[i] = false; for(string s; ~scanf("%s",__s); ){ if(__s[0] == '0') break; s = __s, ++ cnt[dic[s]]; } for(string s; ~scanf("%s",__s); ){ if(__s[0] == '0') break; s = __s, special[dic[s]] = true; }}int french[11][11];int getCombination(int n,int m){ static bool done = false; if(not done){ memset(french,0,sizeof french); for(int i=0,j; i<=10; ++i) for(j=1,french[i][0]=1; j<=i; ++j) french[i][j] = french[i-1][j]+french[i-1][j-1]; done = true; } return french[n][m];}long long dp[2][5][5][5]; /* 对、面子 */long long tmp[2][5][5][5];# define val ((1ll<<(special[kind]*r))*getCombination(chose,r))void moveStatus(int kind,int chose){ FOR(i,0,1) FOR(j,0,4) FOR(x,0,chose) FOR(l,x,4) if(dp[i][j][x][l]) FOR(r,x,chose) FOR(t,0,r-x) if(r-x-t != 4 and r-x-t != 1 and j+(r-x-t==3)+x <= 4 and not (i&(r-x-t==2))) getMax(tmp[i|(r-x-t==2)][j+(r-x-t==3)+x][l-x][t], dp[i][j][x][l]*val);}# undef val /* val是分数加成 */void solve(){ memset(dp,0,sizeof dp); dp[0][0][0][0] = 1; for(int i=1; i<=9; ++i){ /* 处理万子 */ memset(tmp,0,sizeof tmp); moveStatus(i,4-cnt[i]); swap(dp,tmp); } memset(tmp,0,sizeof tmp); FOR(i,0,1) FOR(j,0,4) tmp[i][j][0][0] = dp[i][j][0][0]; swap(tmp,dp); for(int i=10; i<=18; ++i){ memset(tmp,0,sizeof tmp); moveStatus(i,4-cnt[i]); swap(dp,tmp); } memset(tmp,0,sizeof tmp); FOR(i,0,1) FOR(j,0,4) tmp[i][j][0][0] = dp[i][j][0][0]; swap(tmp,dp); for(int i=19; i<=27; ++i){ memset(tmp,0,sizeof tmp); moveStatus(i,4-cnt[i]); swap(dp,tmp); } memset(tmp,0,sizeof tmp); FOR(i,0,1) FOR(j,0,4) tmp[i][j][0][0] = dp[i][j][0][0]; swap(tmp,dp); for(int i=28; i<=34; ++i){ memset(tmp,0,sizeof tmp); moveStatus(i,4-cnt[i]); swap(dp,tmp); memset(tmp,0,sizeof tmp); FOR(i,0,1) FOR(j,0,4) tmp[i][j][0][0] = dp[i][j][0][0]; swap(tmp,dp); } /* 特殊处理:国士无双 */ long long ans = 0, assist = 1; for(int i=1; i<=34; ++i) if(gsws[i]) assist *= (4-cnt[i])*(1<
>1); ans *= 13; /* 国士无双的情况处理终了 */ /* 特殊处理:七个对子 */ int profit[35]; for(int i=1; i<=34; ++i) profit[i] = (1<<(special[i]*2))*getCombination(4-cnt[i],2); sort(profit+1,profit+35,greater
()); assist = 1; for(int i=1; i<=7; ++i) assist *= profit[i]; getMax(ans,assist*7); /* 七个对子的情况处理终了 */ getMax(ans,dp[1][4][0][0]); /* 杠划不来 */ writeint(ans), putchar('\n');}int main(){ // freopen("doraippai.in","r",stdin); // freopen("doraippai.out","w",stdout); prepare(); for(int T=readint(); T; --T) input(), solve(); return 0;}
上一篇:Node.js中模板引擎的使用、POST方式提交数据的获取以及router功能
下一篇:JavaScript实现文本框和密码框placeholder效果(兼容ie8)

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2025年04月16日 17时08分31秒