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;
}
posted @ 2022-12-27 21:48  zxr000  阅读(21)  评论(0编辑  收藏  举报