P5301 [GXOI/GZOI2019]宝牌一大堆
题意:
太长了:https://www.luogu.com.cn/problem/P5301
思路:
https://www.cnblogs.com/do-while-true/p/14993015.html
https://www.luogu.com.cn/blog/genshy/solution-p5301
实现:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int cnt[35];
int bkp[35];
int fac[50], pow2[50];
ll f[35][5][3][5][5][5];
int can[50] = {0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // 标记映射的i对应的牌可不可以当顺子的开头
// 预处理出阶乘
void init()
{
fac[0] = 1;
pow2[0] = 1;
for (int i = 1; i <= 10; i++)
fac[i] = fac[i - 1] * i;
for (int i = 1; i <= 30; i++)
pow2[i] = pow2[i - 1] * 2;
}
// 求 C_x^y
ll C(int x, int y)
{
return fac[x] / fac[y] / fac[x - y];
}
// 求 y 张 x 牌的宝牌增益
ll cbp(int x, int y)
{
return bkp[x] ? pow2[y] : 1;
}
/* 将牌映射
* 1 ->9 1m,2m,...
* 10 ->18 1p,2p,....
* 19 ->27 1s,2s,3s,
* 28:E ,29:S ,30:W ,31:N ,32:Z ,33:B ,34:F
*/
int getpai()
{
char ch = getchar();
if (ch == '0')
return -1;
if (ch == ' ' || ch == '\n')
return 0;
if (ch >= '1' && ch <= '9')
{
char ch2 = getchar();
if (ch2 == 'm')
return ch - '0';
if (ch2 == 'p')
return ch - '0' + 9;
if (ch2 == 's')
return ch - '0' + 18;
}
switch (ch)
{
case 'E':
return 28;
case 'S':
return 29;
case 'W':
return 30;
case 'N':
return 31;
case 'Z':
return 32;
case 'B':
return 33;
case 'F':
return 34;
}
return -1;
}
// 读入已经消耗掉的牌
void read()
{
int x = getpai();
while (x != -1)
{
if (x)
cnt[x]--;
x = getpai();
}
}
// 读入宝牌
void read2()
{
int x = getpai();
while (x != -1)
{
if (x)
bkp[x] = 1;
x = getpai();
}
}
/*
* 计算国士无双情况获得的最大价值
*/
// 国士无双出现的牌对应的编号
int gsws[15] = {0, 1, 9, 10, 18, 19, 27, 28, 29, 30, 31, 32, 33, 34};
ll Gsws()
{
ll sumq = 0, mul = 1, bps = 0;
// 如果存在一张确实则该情况不可能出现
for (int i = 1; i <= 13; i++)
if (cnt[gsws[i]] == 0)
return 0;
// 计算都选一张的价值
for (int i = 1; i <= 13; i++)
{
mul *= cnt[gsws[i]]; // 选一张牌时的方案加成
bps += bkp[gsws[i]];
}
// 枚举选两张的情况
for (int i = 1; i <= 13; i++)
{
if (cnt[gsws[i]] <= 1)
continue;
sumq = max(sumq, mul / cnt[gsws[i]] * C(cnt[gsws[i]], 2) * pow2[bps + bkp[gsws[i]]]);
}
return sumq * 13; // 国士无双额外加成
}
// 计算七对子的最大价值
ll qidvzi()
{
priority_queue<int> q;
for (int i = 1; i <= 34; i++)
{
if (cnt[i] >= 2)
{
if (bkp[i])
q.push(C(cnt[i], 2) * 4);
else
q.push(C(cnt[i], 2));
}
}
// 不足七个的话,就没有这种情况
if (q.size() < 7)
return 0;
ll res = 1, cnt = 0;
while (cnt < 7)
{
cnt++;
res *= q.top();
q.pop();
}
return res * 7; // 七对子额外加成
}
// 计算 4 * 3 + 2
ll dp()
{
memset(f, 0, sizeof f);
f[1][0][0][0][0][0] = 1;
ll res = 0;
for (int i = 1; i <= 34; i++)
{
for (int j = 0; j <= 4; j++)
{
for (int k = 0; k <= 1; k++)
{
for (int u = 0; u <= 4; u++)
{
for (int v = 0; v <= 4; v++)
{
for (int w = 0; w <= 4; w++)
{
if (!f[i][j][k][u][v][w])
continue;
// 雀头
if (k == 0 && u + 2 <= cnt[i])
f[i][j][1][u + 2][v][w] = max(f[i][j][1][u + 2][v][w], (ll)(f[i][j][k][u][v][w] * C(cnt[i], u + 2) / C(cnt[i], u) * (bkp[i] ? 4 : 1)));
// 刻子
if (j + 1 <= 4 && u + 3 <= cnt[i])
f[i][j + 1][k][u + 3][v][w] = max(f[i][j + 1][k][u + 3][v][w], (ll)(f[i][j][k][u][v][w] * C(cnt[i], u + 3) / C(cnt[i], u) * (bkp[i] ? 8 : 1)));
// 顺子
if (j + 1 <= 4 && can[i] && u + 1 <= cnt[i] && v + 1 <= cnt[i + 1] && w + 1 <= cnt[i + 2])
f[i][j + 1][k][u + 1][v + 1][w + 1] = max(f[i][j + 1][k][u + 1][v + 1][w + 1], ll(f[i][j][k][u][v][w] * C(cnt[i], u + 1) / C(cnt[i], u) * C(cnt[i + 1], v + 1) / C(cnt[i + 1], v) * C(cnt[i + 2], w + 1) / C(cnt[i + 2], w) * (bkp[i] ? 2 : 1) * (bkp[i + 1] ? 2 : 1) * (bkp[i + 2] ? 2 : 1)));
// 直接转移
if (i < 34)
f[i + 1][j][k][v][w][0] = max(f[i + 1][j][k][v][w][0], f[i][j][k][u][v][w]);
res = max(res, f[i][j][k][u][v][w]);
}
}
}
}
}
}
ll ans = 0;
for (int i = 1; i <= 34; i++)
{
for (int j = 0; j <= 4; j++)
{
for (int k = 0; k <= 4; k++)
{
for (int u = 0; u <= 4; u++)
{
ans = max(ans, f[i][4][0][j][k][u]);
ans = max(ans, f[i][4][1][j][k][u]);
}
}
}
}
return ans;
return res;
}
int main()
{
int _;
scanf("%d", &_);
init();
while (_--)
{
ll res = 0;
for (int i = 1; i <= 34; i++)
{
cnt[i] = 4;
bkp[i] = 0;
}
read();
read2();
res = max(res, Gsws());
res = max(res, qidvzi());
res = max(res, dp());
printf("%lld\n", res);
}
return 0;
}