zoukankan      html  css  js  c++  java
  • 「题解」洛谷P5301 [GXOI/GZOI2019]宝牌一大堆

    获得可能更优的阅读体验

    快跑。

    本篇文章重点在于整理如何优美地写完这道题。

    先手写张攻略理解下规则。

    发现 (>15) 张的「和牌」方式不需考虑,没有 「([3 imes 4+2])」 的更优。若是宝牌: (inom{4}{4} imes 2<inom{4}{3} imes2);若不是宝牌:(inom{4}{4}<inom{4}{3}),所以 (>15) 张的「和牌」方式中的杠子可以换成刻子,答案更优。

    「宝牌」数对「达成分数」的贡献可以拆开分给每个数。

    现在「和牌」方式仅剩 「([3 imes 4+2])」、「七对子」、「国士无双」 三种方案。前两种分别 dp 一下,后者暴力算答案。

    • 预处理

    为了方便做,先给每种牌一个标号。因人而异,建议万索筒这三个序数牌放在一起编号,因为他们各自可以组成顺子。

    bool baopai[35];
    int cnt[35]; 
    
    //为了方便,我把和计算答案有关的,和输入有关的分别放到namespace中。
     
    namespace Calc {
    	int fac[15], pow2[50];
    	void Cpre() { fac[0] = 1; for(int i = 1; i <= 10; ++i) fac[i] = fac[i-1] * i; pow2[0] = 1; for(int i = 1; i <= 30; ++i) pow2[i] = pow2[i-1]*2; }
    	inline ll C(int x, int y) { return fac[x] / fac[y] / fac[x-y]; }
    	inline ll cbp(int x, int y) { return baopai[x] ? pow2[y] : 1; }
    }
    using namespace Calc;
    namespace How_to_get {
    	//1  -> 9   1m,2m,3m...
    	//10 -> 18  1p,2p,3p...
    	//19 -> 27  1s,2s,3s...
    	//28:E  29:S  30:W  31:N  32:Z  33:B  34:F
    	inline char getcha() { char ch; std::cin >> ch; return ch; }          
    	inline int getpai() {
    		char ch = getcha(); if(ch == '0') return 0;
    		if(ch >= '1' && ch <= '9') {
    			char ch2 = getcha();
    			if(ch2 == 'm') return ch-'0';
    			if(ch2 == 'p') return 9+(ch-'0');
    			if(ch2 == 's') return 18+(ch-'0');
    		}
    		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 0;
    	}
    	void read1() {
    		int x = getpai();
    		while(x) {
    			cnt[x]--;
    			x = getpai();
    		}
    	}
    	void read2() {
    		int x = getpai();
    		while(x) {
    			baopai[x] = 1;
    			x = getpai();
    		}
    	}
    }
    using namespace How_to_get;
    
    • 「国士无双」

    直接暴力做。列出可以「国士无双」的牌的编号,先假装全只选一个算出答案,然后枚举哪张牌选了两遍,算一下答案即可。

    int gsws[15] = {0, 1, 9, 10, 18, 19, 27, 28, 29, 30, 31, 32, 33, 34};
    ll Gsws() {
    	Cpre();
    	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 += baopai[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+baopai[gsws[i]]]);
    	}
    	return sumq * 13;
    }
    
    • ([3 imes 4+2])

    算是最难写的一部分。

    (f_{i,j,k,c1,c2}) 为考虑前 (i) 种牌,(j) 个面子,有 (k) 个雀头,第 (i) 种牌选了 (c_1) 个,第 ((i-1)) 种牌选了 (c_2) 个的最高分数。

    填表法是前面状态转移到当前状态,涉及减法有点绕。考虑刷表法,从当前状态转移到后面状态。值得注意的是选取 (c_1,c_2) 意义为选了几个而不是剩下几个也是避免 dp 的时候关于状态的下标涉及减法,不太好绕得过来。

    有两种 dp 方式,一种是 ((i+1)) 能作为结尾顺顺子的,一种是不能的。(因为选择刷表法,所以枚举 (i) 之后看的牌是 ((i+1)) 号牌。)

    注意到不仅有八筒不能往下顺下去,八万和八索也不能往下顺下去,不能出现八万九万一索顺子的情况

    对于前一种,有以下几种转移方法:

    • 一组刻子;

    • 一组或两组顺子;

    • 一组刻子,一组顺子;

    • 一组雀头;

    • 一组雀头,一组顺子;

    • 一组雀头,两组顺子;

    • 什么也不选。

    因为三个顺子可以转化成三个刻子,所以不需要三个顺子的转移,四个顺子也类似。

    对于后一种,有以下几种转移方法:

    • 一组刻子;

    • 一组雀头;

    • 什么也不选。

    然后就是考验代码能力的环节了:

    void dp1(int l, int r) {
    	for(int i = l; i <= r; ++i)
    		for(int j = 0; j <= 4; ++j)
    			for(int k = 0; k <= 1; ++k)
    					for(int c1 = 0; c1 <= 4; ++c1)
    						for(int c2 = 0; c2 <= 4; ++c2) {
    								if(!f[i][j][k][c1][c2]) continue ;
    								if(j == 4 && k) chkmax(ans, f[i][j][k][c1][c2]), chkmax(sumq1, f[i][j][k][c1][c2]);
    								//刻子*1
    								if(j+1<=4 && cnt[i+1] >= 3)
    									chkmax(f[i+1][j+1][k][3][c1], f[i][j][k][c1][c2] * C(cnt[i+1], 3) * cbp(i+1, 3));
    								//顺子
    								for(int p = 1; p <= 2; ++p) 
    									if(j+p<=4 && i>=2 && cnt[i+1]>=p && c1<=cnt[i]-p && c2<=cnt[i-1]-p)
    										chkmax(f[i+1][j+p][k][1][c1+p], f[i][j][k][c1][c2] / C(cnt[i], c1) / C(cnt[i-1], c2) * C(cnt[i+1], p) * C(cnt[i], c1+p) * C(cnt[i-1], c2+p) * cbp(i+1, p) * cbp(i, p) * cbp(i-1, p));
    								//刻子*1+顺子*1
    								if(j+2<=4 && i>=2 && cnt[i+1] >= 4 && c1 < cnt[i] && c2 < cnt[i-1])
    									chkmax(f[i+1][j+2][k][4][c1+1], f[i][j][k][c1][c2] / C(cnt[i], c1) / C(cnt[i-1], c2) * C(cnt[i+1], 4) * C(cnt[i], c1+1) * C(cnt[i-1], c2+1) * cbp(i+1, 4) * cbp(i, 1) * cbp(i-1, 1));
    								//雀头*1
    								if(cnt[i+1] >= 2 && !k)
    									chkmax(f[i+1][j][1][2][c1], f[i][j][k][c1][c2] * C(cnt[i+1], 2) * cbp(i+1, 2));
    								//雀头*1 + 顺子*1
    								if(j+1<=4 && i>=2 && cnt[i+1] >= 3 && !k && c1 < cnt[i] && c2 < cnt[i-1])
    									chkmax(f[i+1][j+1][1][3][c1+1], f[i][j][k][c1][c2] / C(cnt[i], c1) / C(cnt[i-1], c2) * C(cnt[i+1], 3) * C(cnt[i], c1+1) * C(cnt[i-1], c2+1) * cbp(i+1, 3) * cbp(i, 1) * cbp(i-1, 1));
    								chkmax(f[i+1][j][k][0][c1], f[i][j][k][c1][c2]);
    								//雀头*1 + 顺子*2
    								if(j+2<=4 && i>=2 && cnt[i+1] >= 4 && !k && c1 < cnt[i]-1 && c2 < cnt[i-1]-1)
    									chkmax(f[i+1][j+2][1][4][c1+2], f[i][j][k][c1][c2] / C(cnt[i], c1) / C(cnt[i-1], c2) * C(cnt[i+1], 4) * C(cnt[i], c1+2) * C(cnt[i-1], c2+2) * cbp(i+1, 4) * cbp(i, 2) * cbp(i-1, 2));
    								chkmax(f[i+1][j][k][0][c1], f[i][j][k][c1][c2]);
    							}
    }
    void dp2(int l, int r) {
    	for(int i = l; i <= r; ++i)
    		for(int j = 0; j <= 4; ++j)
    			for(int k = 0; k <= 1; ++k)
    					for(int c1 = 0; c1 <= 4; ++c1)
    						for(int c2 = 0; c2 <= 4; ++c2) {
    								if(!f[i][j][k][c1][c2]) continue ;
    								if(j == 4 && k) chkmax(ans, f[i][j][k][c1][c2]), chkmax(sumq1, f[i][j][k][c1][c2]);
    								//刻子           
    								if(j+1 <= 4 && cnt[i+1] >= 3)
    									chkmax(f[i+1][j+1][k][3][c1], f[i][j][k][c1][c2] * C(cnt[i+1], 3) * cbp(i+1, 3));
    								//雀头 
    								if(cnt[i+1] >= 2 && !k)
    									chkmax(f[i+1][j][1][2][c1], f[i][j][k][c1][c2] * C(cnt[i+1], 2) * cbp(i+1, 2));
    								chkmax(f[i+1][j][k][0][c1], f[i][j][k][c1][c2]);
    						}
    }
    

    处理当前询问的部分有关「([3 imes 4+2])」的:

    	f[0][0][0][0][0] = 1;
    	dp1(0, 8);
    	dp2(9, 10);
    	dp1(11, 17);
    	dp2(18, 19);
    	dp1(20, 26);
    	dp2(27, 33);
    	for(int c1 = 0; c1 <= 4; ++c1)
    		for(int c2 = 0; c2 <= 4; ++c2)
    			chkmax(ans, f[34][4][1][c1][c2]);
    
    • 「七对子」

    和上面的类似,不过更为简单。

    (g_{i,j}) 为前 (i) 种牌,(j) 个雀头,的最高分数。

    由于需要不同的雀头,对于一种牌,只能选出一组雀头。

    这样转移就很好转移了。

    	g[0][0] = 7;
    	for(int i = 0; i <= 33; ++i)
    		for(int j = 0; j <= 7; ++j) {
    			if(!g[i][j]) continue ;
    			if(j == 7) chkmax(ans, g[i][j]);
    			if(cnt[i+1] >= 2 && j+1 <= 7)
    				chkmax(g[i+1][j+1], g[i][j] * C(cnt[i+1], 2) * cbp(i+1, 2));
    			chkmax(g[i+1][j], g[i][j]);
    		}
    	chkmax(ans, g[34][7]);
    

    总得来讲,我写这道题大体分为这三步:

    1. 整理题目规则,写一份"攻略",标出重点,坑点,例如该题中顺子的条件,「七对子」要求雀头都不相等。

    2. 分析题目哪些是不必要写的,剩下几部分分别应该怎么解决。

    3. 编写代码,尽量封装来减少代码量,使代码可读性更高,更好调试。

    希望能对你有所帮助。

    完整版代码:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #define pb push_back
    #define fir first
    #define sec second
    #define mp std::make_pair
    typedef std::pair<int, int> pii;
    typedef long long ll;
    template <typename T> T Max(T x, T y) { return x > y ? x : y; }
    template <typename T> T Min(T x, T y) { return x < y ? x : y; }
    template <typename T> T Abs(T x) { return x < 0 ? -x : x; }
    template <typename T> T chkmax(T &x, T y) { return x = x > y ? x : y; }
    template <typename T>
    T &read(T &r) {
    	r = 0; bool w = 0; char ch = getchar();
    	while(ch < '0' || ch > '9') w = ch == '-' ? 1 : 0, ch = getchar();
    	while(ch >= '0' && ch <= '9') r = (r << 3) + (r <<1) + (ch ^ 48), ch = getchar();
    	return r = w ? -r : r;
    }
    
    bool baopai[35];
    int cnt[35];
    
    namespace Calc {
    	int fac[15], pow2[50];
    	void Cpre() { fac[0] = 1; for(int i = 1; i <= 10; ++i) fac[i] = fac[i-1] * i; pow2[0] = 1; for(int i = 1; i <= 30; ++i) pow2[i] = pow2[i-1]*2; }
    	inline ll C(int x, int y) { return fac[x] / fac[y] / fac[x-y]; }
    	inline ll cbp(int x, int y) { return baopai[x] ? pow2[y] : 1; }
    }
    using namespace Calc;
    namespace How_to_get {
    	inline char getcha() { char ch; std::cin >> ch; return ch; }          
    	inline int getpai() {
    		char ch = getcha(); if(ch == '0') return 0;
    		if(ch >= '1' && ch <= '9') {
    			char ch2 = getcha();
    			if(ch2 == 'm') return ch-'0';
    			if(ch2 == 'p') return 9+(ch-'0');
    			if(ch2 == 's') return 18+(ch-'0');
    		}
    		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 0;
    	}
    }
    using namespace How_to_get;
    
    int gsws[15] = {0, 1, 9, 10, 18, 19, 27, 28, 29, 30, 31, 32, 33, 34};
    
    void read1() {
    	int x = getpai();
    	while(x) {
    		cnt[x]--;
    		x = getpai();
    	}
    }
    void read2() {
    	int x = getpai();
    	while(x) {
    		baopai[x] = 1;
    		x = getpai();
    	}
    }
    ll Gsws() {
    	Cpre();
    	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 += baopai[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+baopai[gsws[i]]]);
    	}
    	return sumq * 13;
    }
    
    ll f[40][10][5][6][6], g[40][10];
    ll ans = 0;
    
    	//1  -> 9   1m,2m,3m...
    	//10 -> 18  1p,2p,3p...
    	//19 -> 27  1s,2s,3s...
    	//28:E  29:S  30:W  31:N  32:Z  33:B  34:F 
    void dp1(int l, int r) {
    	for(int i = l; i <= r; ++i)
    		for(int j = 0; j <= 4; ++j)
    			for(int k = 0; k <= 1; ++k)
    					for(int c1 = 0; c1 <= 4; ++c1)
    						for(int c2 = 0; c2 <= 4; ++c2) {
    								if(!f[i][j][k][c1][c2]) continue ;
    								if(j == 4 && k) chkmax(ans, f[i][j][k][c1][c2]);
    								//刻子*1
    								if(j+1<=4 && cnt[i+1] >= 3)
    									chkmax(f[i+1][j+1][k][3][c1], f[i][j][k][c1][c2] * C(cnt[i+1], 3) * cbp(i+1, 3));
    								//顺子
    								for(int p = 1; p <= 2; ++p) 
    									if(j+p<=4 && i>=2 && cnt[i+1]>=p && c1<=cnt[i]-p && c2<=cnt[i-1]-p)
    										chkmax(f[i+1][j+p][k][1][c1+p], f[i][j][k][c1][c2] / C(cnt[i], c1) / C(cnt[i-1], c2) * C(cnt[i+1], p) * C(cnt[i], c1+p) * C(cnt[i-1], c2+p) * cbp(i+1, p) * cbp(i, p) * cbp(i-1, p));
    								//刻子*1+顺子*1
    								if(j+2<=4 && i>=2 && cnt[i+1] >= 4 && c1 < cnt[i] && c2 < cnt[i-1])
    									chkmax(f[i+1][j+2][k][4][c1+1], f[i][j][k][c1][c2] / C(cnt[i], c1) / C(cnt[i-1], c2) * C(cnt[i+1], 4) * C(cnt[i], c1+1) * C(cnt[i-1], c2+1) * cbp(i+1, 4) * cbp(i, 1) * cbp(i-1, 1));
    								//雀头*1
    								if(cnt[i+1] >= 2 && !k)
    									chkmax(f[i+1][j][1][2][c1], f[i][j][k][c1][c2] * C(cnt[i+1], 2) * cbp(i+1, 2));
    								//雀头*1 + 顺子*1
    								if(j+1<=4 && i>=2 && cnt[i+1] >= 3 && !k && c1 < cnt[i] && c2 < cnt[i-1])
    									chkmax(f[i+1][j+1][1][3][c1+1], f[i][j][k][c1][c2] / C(cnt[i], c1) / C(cnt[i-1], c2) * C(cnt[i+1], 3) * C(cnt[i], c1+1) * C(cnt[i-1], c2+1) * cbp(i+1, 3) * cbp(i, 1) * cbp(i-1, 1));
    								chkmax(f[i+1][j][k][0][c1], f[i][j][k][c1][c2]);
    								//雀头*1 + 顺子*2
    								if(j+2<=4 && i>=2 && cnt[i+1] >= 4 && !k && c1 < cnt[i]-1 && c2 < cnt[i-1]-1)
    									chkmax(f[i+1][j+2][1][4][c1+2], f[i][j][k][c1][c2] / C(cnt[i], c1) / C(cnt[i-1], c2) * C(cnt[i+1], 4) * C(cnt[i], c1+2) * C(cnt[i-1], c2+2) * cbp(i+1, 4) * cbp(i, 2) * cbp(i-1, 2));
    								chkmax(f[i+1][j][k][0][c1], f[i][j][k][c1][c2]);
    							}
    }
    void dp2(int l, int r) {
    	for(int i = l; i <= r; ++i)
    		for(int j = 0; j <= 4; ++j)
    			for(int k = 0; k <= 1; ++k)
    					for(int c1 = 0; c1 <= 4; ++c1)
    						for(int c2 = 0; c2 <= 4; ++c2) {
    								if(!f[i][j][k][c1][c2]) continue ;
    								if(j == 4 && k) chkmax(ans, f[i][j][k][c1][c2]);
    								//刻子           
    								if(j+1 <= 4 && cnt[i+1] >= 3)
    									chkmax(f[i+1][j+1][k][3][c1], f[i][j][k][c1][c2] * C(cnt[i+1], 3) * cbp(i+1, 3));
    								//雀头 
    								if(cnt[i+1] >= 2 && !k)
    									chkmax(f[i+1][j][1][2][c1], f[i][j][k][c1][c2] * C(cnt[i+1], 2) * cbp(i+1, 2));
    								chkmax(f[i+1][j][k][0][c1], f[i][j][k][c1][c2]);
    						}
    }
    void solve() {
    	for(int i = 1; i <= 34; ++i) cnt[i] = 4, baopai[i] = 0;
    	read1(); read2();
    	ans = 0;
    	ans = Max(ans, Gsws());
    	for(int i = 1; i <= 34; ++i)
    		for(int j = 0; j <= 4; ++j)
    			for(int k = 0; k <= 1; ++k)
    					for(int c1 = 0; c1 <= 4; ++c1)
    						for(int c2 = 0; c2 <= 4; ++c2)
    								f[i][j][k][c1][c2] = 0;
    	for(int i = 1; i <= 34; ++i)
    		for(int j = 0; j <= 7; ++j)
    					g[i][j] = 0;
    	f[0][0][0][0][0] = 1;
    	dp1(0, 8);
    	dp2(9, 10);
    	dp1(11, 17);
    	dp2(18, 19);
    	dp1(20, 26);
    	dp2(27, 33);
    	for(int c1 = 0; c1 <= 4; ++c1)
    		for(int c2 = 0; c2 <= 4; ++c2)
    			chkmax(ans, f[34][4][1][c1][c2]);
    	g[0][0] = 7;
    	for(int i = 0; i <= 33; ++i)
    		for(int j = 0; j <= 7; ++j) {
    			if(!g[i][j]) continue ;
    			if(j == 7) chkmax(ans, g[i][j]);
    			if(cnt[i+1] >= 2 && j+1 <= 7)
    				chkmax(g[i+1][j+1], g[i][j] * C(cnt[i+1], 2) * cbp(i+1, 2));
    			chkmax(g[i+1][j], g[i][j]);
    		}
    	chkmax(ans, g[34][7]);
    	printf("%lld
    ", ans);
    }
    
    signed main() { //freopen("in.txt", "r", stdin);
    	int T; read(T);
    	while(T--) solve();
    	return 0;
    }
    
  • 相关阅读:
    你看,那个人好像一条狗哎
    我竟然被抓去做了比特币挖矿工
    聊聊JAVA中 String类为什么不可变
    三分钟深入TT猫之故障转移
    shell实现两个数的相加
    shell截取字符串的方法
    Vi命令:如何删除全部内容?
    bash中不可以用字符串做数组下标
    awk打印出当前行的上一行
    awk同时处理多个文件
  • 原文地址:https://www.cnblogs.com/do-while-true/p/14993015.html
Copyright © 2011-2022 走看看