题目地址:P5301 [GXOI/GZOI2019]宝牌一大堆

这里是官方题解(by lydrainbowcat)

部分分

直接搜索可以得到暴力分,因为所有和牌方案一共只有一千万左右,稍微优化一下数据少的测试点可以跑过

\(3\) ~ \(7\) 已经打出的,不需要考虑顺子,可以跟七对子类似直接算

正解

预处理组合数

DP 计算 \(3*4+2\) :

前 \(i\) 种牌,选了 \(j\) 组面子, \(k\) 组雀头,其中第 \(i - 2\) ~ \(i\) 种牌分别选了 \(l,m,n\) 张时,前 \(i - 3\) 种牌可以获得的最大价值

转移:刻子、顺子、杠子、雀头四个转移,详见 stdcalc_normal 函数的注释

直接计算七对子、国士无双

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<ctime>
using namespace std;
// 1m 2m ... 9m 1p 2p ... 9p 1s 2s ... 9s E S W N Z B F
const bool shuntsu[35] = { 0,0,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 };
const int yao[13] = { 1,9,10,18,19,27,28,29,30,31,32,33,34 };
int a[35], b[35], c[5][5], d[35];
long long f[35][5][2][5][5][5], chitoi[35][8], ans;

int label(char str[3]) {
    if (str[0] == 'E') return 28;
    if (str[0] == 'S') return 29;
    if (str[0] == 'W') return 30;
    if (str[0] == 'N') return 31;
    if (str[0] == 'Z') return 32;
    if (str[0] == 'B') return 33;
    if (str[0] == 'F') return 34;
    if (str[1] == 'm') return str[0] - '0';
    if (str[1] == 'p') return 9 + str[0] - '0';
    if (str[1] == 's') return 18 + str[0] - '0';
    return 0;
}

inline void update(long long &dst, long long src) {
    dst = max(dst, src);
}

inline int dora(int idx, int cnt) {
    return d[idx] ? 1 << cnt : 1;
}

long long calc_normal() {
    long long ret = 0;
    memset(f, 0, sizeof(f));
    f[1][0][0][0][0][0] = 1;
    // 前i种牌,选了j组面子,k组雀头,其中第i-2~i种牌分别选了l,m,n张时,前i-3种牌可以获得的最大价值
    for (int i = 1; i <= 34; i++)
        for (int j = 0; j <= 4; j++)
            for (int k = 0; k <= 1; k++)
                for (int l = 0; l <= 4; l++)
                    for (int m = 0; m <= 4; m++)
                        for (int n = 0; n <= 4; n++) {
                            long long now;
                            if ((now = f[i][j][k][l][m][n]) == 0) continue;
                            // 去选下一种牌
                            if (i < 34)
                                update(f[i + 1][j][k][m][n][0], now * (i > 2 ? c[a[i - 2]][l] : 1) * dora(i - 2, l));
                            // 组成刻子
                            if (j < 4 && a[i] - n >= 3)
                                update(f[i][j + 1][k][l][m][n + 3], now);
                            // 组成杠子,杠子也可以算作面子来统计
                            if (j < 4 && a[i] - n >= 4)
                                update(f[i][j + 1][k][l][m][n + 4], now);
                            // 组成顺子
                            if (j < 4 && shuntsu[i] && a[i] - n && a[i - 1] - m && a[i - 2] - l)
                                update(f[i][j + 1][k][l + 1][m + 1][n + 1], now);
                            // 组成雀头
                            if (k < 1 && a[i] - n >= 2)
                                update(f[i][j][k + 1][l][m][n + 2], now);
                            if (i == 34 && j == 4 && k == 1)
                                update(ret, now * c[a[i]][n] * c[a[i - 1]][m] * c[a[i - 2]][l] * dora(i, n) * dora(i - 1, m) * dora(i - 2, l));
                        }
    return ret;
}

long long calc_chitoi() {
    memset(chitoi, 0, sizeof(chitoi));
    chitoi[0][0] = 1;
    for (int i = 1; i <= 34; i++)
        for (int j = 0; j <= 7; j++) {
            if (chitoi[i - 1][j] == 0) continue;
            update(chitoi[i][j], chitoi[i - 1][j]);
            if (j < 7) update(chitoi[i][j + 1], chitoi[i - 1][j] * c[a[i]][2] * dora(i, 2));
        }
    return chitoi[34][7] * 7;
}

long long calc_kokushi() {
    long long ret = 0;
    for (int i = 0; i < 13; i++) {
        if (a[yao[i]] == 0) return 0;
        if (a[yao[i]] == 1) continue;
        long long temp = c[a[yao[i]]][2] * dora(yao[i], 2);
        for (int j = 0; j < 13; j++) {
            if (i == j) continue;
            temp = temp * a[yao[j]] * dora(yao[j], 1);
        }
        update(ret, temp * 13);
    }
    return ret;
}

/*void dfs(int x, int m, int n) {
    if (m == 4 && n == 1 || m == 0 && n == 7) {
        long long temp = n;
        for (int i = 1; i <= 34; i++)
            if (a[i] < b[i]) temp = temp * c[b[i]][b[i] - a[i]] * dora(i, b[i] - a[i]);
        update(ans, temp);
        return;
    }
    if (x > 34) return;
    if (n > 1 && m) return;
    dfs(x + 1, m, n);
    if (m < 4 && a[x] >= 4) { // 杠
        a[x] -= 4;
        dfs(x, m + 1, n);
        a[x] += 4;
    }
    if (m < 4 && a[x] >= 3) { // 刻
        a[x] -= 3;
        dfs(x, m + 1, n);
        a[x] += 3;
    }
    if (n < 1 && a[x] >= 2) { // 雀头
        a[x] -= 2;
        dfs(x, m, n + 1);
        a[x] += 2;
    }
    if (m<= 4 && shuntsu[x] && a[x] && a[x - 1] && a[x - 2]) { // 顺子
        a[x] -= 1, a[x - 1] -= 1, a[x - 2] -= 1;
        dfs(x, m + 1, n);
        a[x] += 1, a[x - 1] += 1, a[x - 2] += 1;
    }
    if (m == 0 && a[x] >= 2 && n > 0 && n < 7) {
        a[x] -= 2;
        dfs(x + 1, m, n + 1);
        a[x] += 2;
    }
}*/

int main() {
    for (int i = 0; i <= 5; i++) c[i][0] = 1;
    for (int i = 1; i <= 5; i++)
        for (int j = 1; j <= 5; j++)
            c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
    char str[3];
    int T;
    cin >> T;
    while (T--) {
        for (int i = 1; i <= 34; i++) a[i] = 4;
        while (scanf("%s", str) != EOF && str[0] != '0') a[label(str)]--;
        memset(d, 0, sizeof(d));
        while (scanf("%s", str) != EOF && str[0] != '0') d[label(str)] = 1;
        // ans = 0;
        // for (int i = 1; i <= 34; i++) b[i] = a[i];
        // dfs(1, 0, 0);
        // if (ans != calc_normal() && ans != calc_chitoi()) while (1);
        ans = 0;
        update(ans, calc_normal());
        update(ans, calc_chitoi());
        update(ans, calc_kokushi());
        cout << ans << endl;
    }
}

P5301 [GXOI/GZOI2019]宝牌一大堆的更多相关文章

  1. luogu P5301 [GXOI/GZOI2019]宝牌一大堆

    传送门 wdnm又是打麻将 首先国土无双可以直接枚举哪种牌用了\(2\)次算贡献,然后\(7\)个对子可以把每种牌的对子贡献排序,取最大的\(7\)个,剩下的牌直接暴力枚举是不行的,考虑dp,设\(f ...

  2. 【题解】Luogu P5301 [GXOI/GZOI2019]宝牌一大堆

    原题传送门 首先先要学会麻将,然后会发现就是一个暴力dp,分三种情况考虑: 1.非七对子国士无双,设\(dp_{i,j,k,a,b}\)表示看到了第\(i\)种牌,一共有\(j\)个\(i-1\)开头 ...

  3. [LOJ3084][GXOI/GZOI2019]宝牌一大堆——DP

    题目链接: [GXOI/GZOI2019]宝牌一大堆 求最大值容易想到$DP$,但如果将$7$种和牌都考虑进来的话,$DP$状态不好设,我们将比较特殊的七小对和国士无双单独求,其他的进行$DP$. 观 ...

  4. 【BZOJ5503】[GXOI/GZOI2019]宝牌一大堆(动态规划)

    [BZOJ5503][GXOI/GZOI2019]宝牌一大堆(动态规划) 题面 BZOJ 洛谷 题解 首先特殊牌型直接特判. 然后剩下的部分可以直接\(dp\),直接把所有可以存的全部带进去大力\(d ...

  5. [GXOI/GZOI2019]宝牌一大堆(dp)

    luogu     bzoj 这个麻将题还算挺友善的,比隔壁zjoi的要好得多... 比较正常的做法是五维dp 但事实上六维dp也是完全不会被卡的 七对子选权值最高的七个,国士无双直接$13^2$暴力 ...

  6. 题解 P5301 【[GXOI/GZOI2019]宝牌一大堆】

    这道题除了非常恶心以外也没有什么非常让人恶心的地方 当然一定要说有的话还是有的,就是这题和咱 ZJOI 的 mahjong 真的是好像的说~ 于是就想说这道题出题人应该被 锕 掉 noteskey 整 ...

  7. [GXOI/GZOI2019]宝牌一大堆

    感觉比ZJOI的麻将要休闲很多啊. 这个题就是一个最优化问题,没有面子的特殊牌型可以直接用复杂度较低的贪心判掉. 有面子的话就是一个经典dp.(曾经还在ZJOI写过这个毒瘤东西 大概就是存一下对子,面 ...

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

    题面 好像ZJOI也考了一道麻将, 这是要发扬中华民族的赌博传统吗??? 暴搜都不会打, 看到题目就自闭了, 考完出来之后看题解, \(dp\), 可惜自己想不出来... 对于国士无双(脑子中闪过了韩 ...

  9. [GX/GZOI2019]宝牌一大堆(DP)

    出这种麻将题有意思吗? 乍看很难实则很水,就是麻将式DP,想必大家很熟悉了吧.首先把“国士无双”和“七对子”两种牌型判掉,然后观察牌胡的形式,发现每多一张牌实际上就是把1个面子变成1个杠子,然后可以直 ...

随机推荐

  1. 《css世界》笔记之流、元素与基本尺寸

    1. 块级元素 基本特性:就是一个水平流上只能单独显示一个元素,多个块级元素则换行显示. 块级元素和"display 为block 的元素"不是一个概念,display:list- ...

  2. Python--day11(函数的参数)

    今日主要内容 1.  函数的参数 2.  函数的嵌套调用 1.  形参与实参 1.  参数介绍: 函数为什么要有参数:因为内部的函数体需要外部的数据 怎样定义函数的参数:在定义函数阶段,函数名在后面( ...

  3. 求导程序编写(oo-java编程)

    本单元的任务为求导. 即将一个含自变量x的多项式F求导成为另外一个含自变量x的多项式f.使得 dF/dx = f 为降低我们的难度,这个任务被分解成了三个阶段: (1)对幂函数进行求导(不允许嵌套) ...

  4. VS2019 离线安装方法详解

    本文详细介绍了 VS2019 离线安装的相关步骤,以桌面开发为主下载 C++桌面开发..NET 桌面开发相关的工作负载.MFC 可选组件及帮助查看器. 工作负载(Workload) 离线安装需要先根据 ...

  5. 01构建第一个SpringBoot工程

    第一篇:构建第一个SpringBoot工程 文章指导 学习笔记 学习代码 创建项目 创建工程:Idea-> new Project ->Spring Initializr ->填写g ...

  6. 如何在Linux中轻松删除源安装的软件包?

    第1步:安装Stow 在这个例子中,我们使用的是CentOS,因此我们需要扩展的EPEL库.您可以使用以下命令安装它们:yum install epel-release然后,下面这段命令:yum in ...

  7. jsp假分页

    假分页:从数据库中取出所有的数据,然后分页在界面上显示.访问一次数据库,但由于选择的数据量比较大,所以第一次花费时间比较长,但之后每一页的显示都是直接.快速的,避免对数据库的多次访问. 真分页:确定要 ...

  8. ABP中的Filter(上)

    这个部分我打算用上下两个部分来将整个结构来讲完,在我们读ABP中的代码之后我们一直有一个疑问?在ABP中为什么要定义Interceptor和Filter,甚至这两者之间我们都能找到一些对应关系,比如: ...

  9. C# 使用CsvHelper读取.csv文件

    1,先到包管理器下载 安装CsvHelper. 2,创建一个与csv文件字段名称相同的类 public class SurveyInfoModel { public string DIST_CD { ...

  10. OO_第二单元总结

    第二次总结博客(电梯单元) 16071070 陈泽寅 2019.4.20 一:多线程实验初感 这个单元是多线程设计的实践单元,主要让我们运用多线程的原理与思想去完成一个模拟电梯运行的策略.从最开始的单 ...