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

这道题除了非常恶心以外也没有什么非常让人恶心的地方

当然一定要说有的话还是有的,就是这题和咱 ZJOI 的 mahjong 真的是好像的说~

于是就想说这道题出题人应该被 锕 掉

noteskey

整体的思路就是特判国士无双和七对子,然后 dp 搞普通的胡牌

dp 状态设计和楼上大佬说的一样,就是用一个五维的 \(f[i][j][k][l][p]\) 表示当前处理了前 i 种类型的牌,存在 j 个 面子/杠子 ,以 i-1 开头的顺子要选 k 个,以 i 开头的面子要选 l 个,以及当前是否有 雀头 (用 p 表示)

然后转移就非常的暴力了,反正这里的数据范围也比较小,枚举下状态转移就好了

总的来说就是道 语文 + 码农 + dp 题,虽说没什么思维难度但我不见得能想出来

watch out

这题的字符串读入还是比较毒瘤的...要稍微注意一下不然可能会出事

code

这压行是同样的味道呢~

//by Judge
#include<bits/stdc++.h>
#define Rg register
#define fp(i,a,b) for(Rg int i=(a),I=(b)+1;i<I;++i)
#define ll long long
using namespace std;
#ifndef Judge
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
char buf[1<<21],*p1=buf,*p2=buf;
inline bool cmax(ll& a,ll b){return a<b?a=b,1:0;}
inline int read(){ int x=0,f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f;
} inline void reads(string& s){ char c=getchar();
    for(;!isalpha(c)&&!isdigit(c);c=getchar()); s="";
    for(;isalpha(c)||isdigit(c);c=getchar()) s+=c;
} char sr[1<<21],z[20];int CCF=-1,Z;
inline void Ot(){fwrite(sr,1,CCF+1,stdout),CCF=-1;}
inline void print(ll x,char chr='\n'){
    if(CCF>1<<20)Ot();if(x<0)sr[++CCF]=45,x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++CCF]=z[Z],--Z);sr[++CCF]=chr;
} int t,cnt,a[41],b[41],C[5][5]; ll f[41][5][3][3][2],tp[41];
int gs[14]={0,1,9,10,18,19,27,28,29,30,31,32,33,34}; string c;
inline int id(){ if(c[0]=='B') return 34;
    if(c[0]=='E') return 28; if(c[0]=='S') return 29; if(c[0]=='W') return 30;
    if(c[0]=='N') return 31; if(c[0]=='Z') return 32; if(c[0]=='F') return 33;
    if(c[1]=='m') return c[0]-48; if(c[1]=='p') return c[0]-39; return c[0]-30;
}
int main(){ C[0][0]=1;
    fp(i,1,4){ C[i][0]=1;
        fp(j,1,i) C[i][j]=C[i-1][j-1]+C[i-1][j];
    }
    fp(T,1,read()){
        memset(a,0,sizeof a);
        memset(b,0,sizeof b);
        memset(f,0,sizeof f);
        while(1){ reads(c);
            if(c[0]=='0') break;
            else ++a[id()];
        }
        while(1){ reads(c);
            if(c[0]=='0') break;
            else b[id()]=1;
        }
        fp(i,1,34) a[i]=4-a[i];
        ll ans=0;
        fp(i,1,13){ ll tmp=1; //枚举出现两次的牌
            fp(j,1,13) //枚举 13 种牌
                if(i==j)
                    if(a[gs[j]]<2) tmp=0; //如果数量不够就让 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);
            cmax(ans,tmp*13);
        }
        cnt=0;
        fp(i,1,34) if(a[i]>=2) tp[++cnt]=C[a[i]][2]*(b[i]?4:1);
        if(cnt>=7){ //如果牌数大于等于 2 的不止 7 张就可以构成七对子
            sort(tp+1,tp+1+cnt); ll tmp=1; //选出权最大的 7 种牌
            fp(i,cnt-6,cnt) tmp*=tp[i]; //累乘贡献
            cmax(ans,tmp*7);
        }
        f[0][0][0][0][0]=1; //初始化边界
        fp(i,0,33) fp(j,0,4) for(Rg int k=0;k<3&&j+k<=4;++k){
            if(k>=1&&(i==9||i==18||i>=27)) break; //不合法开头无法构成顺子
            for(Rg int l=0;l<3&&j+k+l<=4;++l){
                if(l>=1&&(i==9||i==18||i==27)) break;
                if(f[i][j][k][l][0]||f[i][j][k][l][1]) fp(u,k+l,a[i+1]){
                    ll tmp=C[a[i+1]][u]*(b[i+1]?(1<<u):1); //计算贡献
                    // 四种转移
                    if(j+u<=4&&u-k-l<3)
                        cmax(f[i+1][j+k][l][u-k-l][0],f[i][j][k][l][0]*tmp),
                        cmax(f[i+1][j+k][l][u-k-l][1],f[i][j][k][l][1]*tmp);
                    if(u-k-l-2>=0&&j+u-2<=4)
                        cmax(f[i+1][j+k][l][u-k-l-2][1],f[i][j][k][l][0]*tmp);
                    if(u-k-l-3>=0&&j+u-2<=4)
                        cmax(f[i+1][j+k+1][l][u-k-l-3][0],f[i][j][k][l][0]*tmp),
                        cmax(f[i+1][j+k+1][l][u-k-l-3][1],f[i][j][k][l][1]*tmp);
                    if(u==4&&!k&&!l&&j<=3)
                        cmax(f[i+1][j+1][0][0][0],f[i][j][k][l][0]*tmp),
                        cmax(f[i+1][j+1][0][0][1],f[i][j][k][l][1]*tmp);
                }
            }
        }
        cmax(ans,f[34][4][0][0][1]),print(ans);
    } return Ot(),0;
}

原文地址:https://www.cnblogs.com/Judge/p/10749200.html

时间: 2024-11-13 09:45:29

题解 P5301 【[GXOI/GZOI2019]宝牌一大堆】的相关文章

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

luogu     bzoj 这个麻将题还算挺友善的,比隔壁zjoi的要好得多... 比较正常的做法是五维dp 但事实上六维dp也是完全不会被卡的 七对子选权值最高的七个,国士无双直接$13^2$暴力 $dp[i][j][0/1][k][l][m]$表示枚举到了第i张牌,已经凑了j个面子,有无雀头,第i张牌已经用了k张,第i+1张牌用了l张,第i+2张牌用了m张,直接暴力转移... 然后你会得到50... 当然需要优化. 优化1: 枚举到dp值为0的直接continue,这样的不合法牌型有很多可

习题:宝牌一大堆(DP&amp;卡常)

题目 传送门 思路 这道题主要是状态不好想和题目不好理解 如果你和笔者一样没有接触过麻将 我们首先将整个麻将进行hash处理 定义\(dp_{i,j,k,l,m,n}\)为前i种牌,杠子和面子总共的数量为j,雀子的数量为k,第\(i-2\)种牌的数量为l,第\(i-1\)种牌的数量为m,第种i牌的数量为n 需要特别注意的是,DP的值是不将i-2~i这三种牌考虑进去的 那么可以写出转移 \(\begin{cases}dp_{i,j+1,k,l+1,m+1,n+1}(\mbox{顺子})\\dp_{

题解:[GXOI/GZOI2019]旅行者

调这个题调了两个月,被自己蠢哭 题意: 给一个有向图,一组关键点,求关键点之间的最短的距离 Solution: 这个题目有两种做法,分别是 $nlogn$ 和 $nlog^2n$ 的 首先说 $nlogn$ 的官方做法,我们考虑多源迪杰斯特拉 正图上从 k 个关键点出发跑 $dijkstra$ ,记某个点离最近的关键点距离为 $dis[0][i]$ 反图上也从 k 个关键点出发跑 $dijkstra$ ,距离记为 $dis[1][i]$ 枚举正图中的边 $u->v: w$, 用 $dis[0][

题解-GXOI/GZOI2019 特技飞行

Problem loj3085 bzoj不放题面差评 题意概要:给出两条竖直直线,再给出 \(n\) 架飞机的初始航线:一条接通这两条直线的线段,保证航线交点不在两条直线上.现要求安排所有飞机在航线相交处做特技: 擦身而过:两架飞机按原方向线路继续前进,一次得分 \(b\) 对向交换:两架飞机交换线路继续前进,一次得分 \(a\) 另外,给定 \(k\) 个边界与坐标轴成 \(45°\)角 的正方形,若一次特技被至少一个正方形囊括,则总得分加 \(c\) 现要求决策每次相遇做的特技,求最大/最小

网络麻将的宝牌设计

今天在帮客户改东北乾安的麻将,乾安麻将的宝牌比效复杂,需求如下:6.1 宝牌:可以替代任何牌的"万能牌".6.2 宝牌产生:牌墙中的最后一张牌为"宝"牌.6.3 宝牌不能查看.6.4 换宝:宝牌被宝杠以后,牌墙中倒数第2张牌为宝牌.6.5 用户听牌后才能使用宝牌. 开始搞了,首选给宝牌定一个变量:看了一下代码,直接把财神当宝牌用了. 首先在开始游戏的地方把宝牌重置. 在用户听牌的动作上加一个读取宝牌的代码,从库存牌变量里取最后一张,这里注意,库存牌里最后一张的key

Loj #3085. 「GXOI / GZOI2019」特技飞行

Loj #3085. 「GXOI / GZOI2019」特技飞行 题目描述 公元 \(9012\) 年,Z 市的航空基地计划举行一场特技飞行表演.表演的场地可以看作一个二维平面直角坐标系,其中横坐标代表着水平位置,纵坐标代表着飞行高度. 在最初的计划中,这 \(n\) 架飞机首先会飞行到起点 \(x = x_{st}\) 处,其中第 \(i\) 架飞机在起点处的高度为 \(y_{i,0}\).它们的目标是终点 \(x = x_{ed}\) 处,其中第 \(i\) 架飞机在终点处的高度应为 \(y

[LuoguP5305][GXOI/GZOI2019]旧词 (树链剖分)

[GXOI/GZOI2019]旧词 (树链剖分) 题面 给定一棵 \(n\)个点的有根树,节点标号 \([1,n]\),1号节点为根. 给定常数\(k\) 给定\(Q\)个询问,每次询问给定\(x,y\),求:\(\sum_{i=1}^x \mathrm{deep}(\mathrm{lca}(i,y)) \mod 998244353\) 分析 此题为[BZOJ3626] [LNOI2014]LCA(树链剖分)的加强版. 考虑原来的做法(k=1):我们把i到根的路径上所有点+1,y到根路径上的权值

[GXOI/GZOI2019]与或和

/* 显然位之间会互不影响, 然后分位来统计, 显然&只有全1才有贡献, 显然|只有全0才没贡献 分别n^2处理即可 */ #include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<queue> #define ll long long #define M 1010 using namespace std; int read() {

[GXOI/GZOI2019]旅行者

就我感觉这道题很神仙吗/kel 仔细想想应该也是一种适用范围挺广的做法. 考虑我们可以通过dijkstra在O(nlogn)求出一个点集到另外一个点集的最短路. 那么我们可以通过一些划分点集的方式使得每一对点都被计算一次. 考虑按照二进制划分. 两个不同的数至少有一个二进制位不同. 按照每一个二进制位01分组,跑logn次dijkstra即可得出答案. #include<bits/stdc++.h> #define N 220000 #define M 1100000 #define eps