[luogu 5301][bzoj 5503] [GXOI/GZOI2019] 宝牌一大堆

题面

好像ZJOI也考了一道麻将, 这是要发扬中华民族的赌博传统吗???

暴搜都不会打, 看到题目就自闭了, 考完出来之后看题解, \(dp\), 可惜自己想不出来...

对于国士无双(脑子中闪过了韩信)和七对子进行特判, 国士无双枚举哪张牌选两张即可, 七对子的话对于每种牌, 如果该种牌可以凑对子, 就将它凑对子对答案的贡献算出来, 排序后贪心地选七个最大的即可

国士无双

		for(int i = 1; i <= 13; i++)
		{
			long long tmp = 1; 
			for(int j = 1; j <= 13; j++)
			{
				if(i == j)
				{
					if(a[gs[j]] < 2) tmp = 0; 
					else tmp *= c[a[gs[j]]][2] * (b[gs[j]] ? 4 : 1); 
				}
				else
				{
					if(a[gs[j]] < 1) tmp = 0;
					else tmp *= c[a[gs[j]]][1] * (b[gs[j]] ? 2 : 1); 
				}
			}
			ans = max(ans, tmp * 13); 
		}

七对子

		cnt = 0; 
		for(int i = 1; i <= 34; i++) if(a[i] >= 2) tp[++cnt] = c[a[i]][2] * (b[i] ? 4 : 1);
		if(cnt >= 7)
		{
			sort(tp + 1, tp + cnt + 1); 
			long long tmp = 1; 
			for(int i = cnt; i > cnt - 7; i--) tmp *= tp[i]; 
			ans = max(ans, tmp * 7); 
		}

\(f[i][j][k][l][0/1]\)代表已经选完了前\(i\)种牌, 有\(j\)个顺子/刻子/杠子, 以\(i\) - \(1\)开头的顺子有\(k\)个, 以\(i\)开头的顺子有\(l\)个, 有没有选雀头的最大分数.

其中, \(k\)\(l\)都应该不大于2, 因为当\(k\)\(l\)中有一个大于2时, 我们就可以把些看做三个杠子或者三个刻子, 这可以直接记录到\(j\)里面去.

所以我们得到下面几个转移式(这个题顺推似乎要好推一些(感谢题解, 滑稽)):

		for(int i = 0; i < 34; i++)
		{
			for(int j = 0; j <= 4; j++)
			{
				for(int k = 0; k < 3 && j + k <= 4; k++)
				{
					if(k && (i == 9 || i == 18 || i >= 27)) break;  //注意, 当i=9或i=18或i>=27时不能够由这种牌为开头组成顺子
					for(int l = 0; l < 3 && j + k + l <= 4; l++)
					{
						if(l && (i == 9 || i == 18 || i >= 27)) break; //同上
						if(!f[i][j][k][l][0] && !f[i][j][k][l][1]) continue; 
						for(int x = k + l; x <= a[i + 1]; x++)
						{
							long long tmp = c[a[i + 1]][x] * (b[i + 1] ? (1 << x) : 1); 
							if(x == 4 && !k && !l && j <= 3)
							{
								f[i + 1][j + 1][0][0][0] = max(f[i + 1][j + 1][0][0][0], f[i][j][k][l][0] * tmp);
								f[i + 1][j + 1][0][0][1] = max(f[i + 1][j + 1][0][0][1], f[i][j][k][l][1] * tmp); 
							}
                            //凑一个杠子, 条件是x - k - l >= 4, 又由于x <= 4, 所以x = 4, 并且j + 1 <= 4, 这个1是凑出来的杠子, 最后的面子和杠子总和不能超过4, 由于l, k均为0, 所以第i + 1个的情况也为0, 没有以i为开头的顺子, 也没有以i + 1为开头的顺子, 全拿去凑杠子去了
							if(x - k - l >= 3 && j + x - 2 <= 4)
							{
								f[i + 1][j + k + 1][l][x - k - l - 3][0] = max(f[i + 1][j + k + 1][l][x - k - l - 3][0], f[i][j][k][l][0] * tmp);
								f[i + 1][j + k + 1][l][x - k - l - 3][1] = max(f[i + 1][j + k + 1][l][x - k - l - 3][1], f[i][j][k][l][1] * tmp); 
							}
                            //凑一个刻子, 那么应该满足的条件是x - k - l >= 3, 即在满足k与l两个要求后剩余的牌还能够拿出来凑成一个杠子, 那么最后到第i + 1种牌时以i + 1为开头的数量应该是x - k - l - 3, 凑完顺子和刻子之后剩下来的当面子, 此时杠子面子数量总和为j + k + 1
							if(x - k - l >= 2 && j + x - 2 <= 4) 
								f[i + 1][j + k][l][x - k - l - 2][1] = max(f[i + 1][j + k][l][x - k - l - 2][1], f[i][j][k][l][0] * tmp);
                            //拿当前牌凑一个雀头, 前提条件是没有雀头, 类比上面的情况分析可知条件分别为x - k - l >= 2, j + x - 2 <= 4, 这里需要注意一下的是由于雀头不算在杠子和面子的总和中, 所以新增的数量便是k, 以i + 1为开头的顺子数量为x - k - l - 2
							if(j + x <= 4 && x - k - l < 3)
							{
								f[i + 1][j + k][l][x - k - l][0] = max(f[i + 1][j + k][l][x - k - l][0], f[i][j][k][l][0] * tmp);
								f[i + 1][j + k][l][x - k - l][1] = max(f[i + 1][j + k][l][x - k - l][1], f[i][j][k][l][1] * tmp); 
							}
                            //什么都不凑只拿来做顺子开头的情况, 自己类比上面分析一下即可
						}
					}
				}
			}
		}

最后比较一下, 输出三种中的最大值即可, 下面是完整代码

完整代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

int c[5][5], a[40], T, cnt;
int gs[14] = { 0, 1, 9, 10, 18, 19, 27, 28, 29, 30, 31, 32, 33, 34 }; 
long long tp[40], f[40][5][3][3][2];
bool b[40]; 

inline int read()
{
	int x = 0, w = 1;
	char c = getchar();
	while(c < '0' || c > '9') { if (c == '-') w = -1; c = getchar(); }
	while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
	return x * w;
}

void check1(char c, char ch, int pos) { if(c == ch) a[pos]++; }

void check2(char c, char ch, int pos) { if(c == ch) b[pos] = 1; }

int main()
{
	T = read();
	for(int i = 0; i <= 4; i++)
		for(int j = 0; j <= i; j++)
			c[i][j] = (j ? c[i - 1][j] + c[i - 1][j - 1] : 1);
	while(T--)
	{
		memset(a, 0, sizeof(a)); memset(f, 0, sizeof(f)); memset(b, 0, sizeof(b));
		while(1)
		{
			string s; cin>>s;
			if(s[0] == '0') break;
			check1(s[0], 'E', 28); check1(s[0], 'S', 29); check1(s[0], 'W', 30); check1(s[0], 'N', 31); check1(s[0], 'Z', 32); check1(s[0], 'B', 33);
			check1(s[0], 'F', 34); check1(s[1], 'm', s[0] - '0'); check1(s[1], 'p', s[0] - '0' + 9); check1(s[1], 's', s[0] - '0' + 18); 
		}
		while(1)
		{
			string s; cin>>s;
			if(s[0] == '0') break; 		
			check2(s[0], 'E', 28); check2(s[0], 'S', 29); check2(s[0], 'W', 30); check2(s[0], 'N', 31); check2(s[0], 'Z', 32); check2(s[0], 'B', 33);
			check2(s[0], 'F', 34); check2(s[1], 'm', s[0] - '0'); check2(s[1], 'p', s[0] - '0' + 9); check2(s[1], 's', s[0] - '0' + 18); 
		}
		for(int i = 1; i <= 34; i++) a[i] = 4 - a[i]; 
		long long ans = 0; 
		for(int i = 1; i <= 13; i++)
		{
			long long tmp = 1; 
			for(int j = 1; j <= 13; j++)
			{
				if(i == j)
				{
					if(a[gs[j]] < 2) tmp = 0; 
					else tmp *= c[a[gs[j]]][2] * (b[gs[j]] ? 4 : 1); 
				}
				else
				{
					if(a[gs[j]] < 1) tmp = 0;
					else tmp *= c[a[gs[j]]][1] * (b[gs[j]] ? 2 : 1); 
				}
			}
			ans = max(ans, tmp * 13); 
		}
		cnt = 0; 
		for(int i = 1; i <= 34; i++) if(a[i] >= 2) tp[++cnt] = c[a[i]][2] * (b[i] ? 4 : 1);
		if(cnt >= 7)
		{
			sort(tp + 1, tp + cnt + 1); 
			long long tmp = 1; 
			for(int i = cnt; i > cnt - 7; i--) tmp *= tp[i]; 
			ans = max(ans, tmp * 7); 
		}
		f[0][0][0][0][0] = 1;
		for(int i = 0; i < 34; i++)
		{
			for(int j = 0; j <= 4; j++)
			{
				for(int k = 0; k < 3 && j + k <= 4; k++)
				{
					if(k && (i == 9 || i == 18 || i >= 27)) break;  
					for(int l = 0; l < 3 && j + k + l <= 4; l++)
					{
						if(l && (i == 9 || i == 18 || i >= 27)) break; 
						if(!f[i][j][k][l][0] && !f[i][j][k][l][1]) continue; 
						for(int x = k + l; x <= a[i + 1]; x++)
						{
							long long tmp = c[a[i + 1]][x] * (b[i + 1] ? (1 << x) : 1); 
							if(x == 4 && !k && !l && j <= 3)
							{
								f[i + 1][j + 1][0][0][0] = max(f[i + 1][j + 1][0][0][0], f[i][j][k][l][0] * tmp);
								f[i + 1][j + 1][0][0][1] = max(f[i + 1][j + 1][0][0][1], f[i][j][k][l][1] * tmp); 
							}
							if(x - k - l >= 3 && j + x - 2 <= 4)
							{
								f[i + 1][j + k + 1][l][x - k - l - 3][0] = max(f[i + 1][j + k + 1][l][x - k - l - 3][0], f[i][j][k][l][0] * tmp);
								f[i + 1][j + k + 1][l][x - k - l - 3][1] = max(f[i + 1][j + k + 1][l][x - k - l - 3][1], f[i][j][k][l][1] * tmp); 
							}
							if(x - k - l >= 2 && j + x - 2 <= 4) 
								f[i + 1][j + k][l][x - k - l - 2][1] = max(f[i + 1][j + k][l][x - k - l - 2][1], f[i][j][k][l][0] * tmp);
							if(j + x <= 4 && x - k - l < 3)
							{
								f[i + 1][j + k][l][x - k - l][0] = max(f[i + 1][j + k][l][x - k - l][0], f[i][j][k][l][0] * tmp);
								f[i + 1][j + k][l][x - k - l][1] = max(f[i + 1][j + k][l][x - k - l][1], f[i][j][k][l][1] * tmp); 
							}
						}
					}
				}
			}
		}
		ans = max(ans, f[34][4][0][0][1]);
		printf("%lld\n", ans); 
	}
	return 0;
}

复杂度自己算吧...

posted @ 2019-04-29 21:48  ztlztl  阅读(136)  评论(0编辑  收藏  举报