「GXOI / GZOI2019」宝牌一大堆 (DP)

题意

LOJ传送门

题解

可以发现「七对子」 和 「国士无双」直接暴力就行了。

唯一的就是剩下的"3*4+2"。

考试的时候写了个爆搜剪枝,开了O2有50pts。写的时候发现可以DP,但是没写。

然后下来写了发现就4个转移。。。

\(dp[i][j][k][a][b][c]\)表示当考虑前\(i\)张牌,有\(j\)个雀头,\(k\)个面子,\(i-2\)用了\(a\)张,\(i-1\)用了\(b\)张,\(i\)用了\(c\)张时,前\(i-3\)张牌的最大分数。

注意这里是"前\(i-3\)张牌的最大分数",也就是没有计算\(a,b,c\)这最后\(3\)种牌的贡献。

有个性质就是杠子一定不会选,因为选杠子一定劣于面子。
因为\(C(4,4)=1<C(4,3)=4\),就算这张牌是宝牌,杠子的贡献是\(C(4,4)*2^4<C(4,3)*2^3\)

所以我们没有考虑杠子。

上代码

CODE

luogu上开了O2才过

#include <bits/stdc++.h>
using namespace std;
inline int getid(char *s) {
	if(isdigit(s[0])) {
		int re = s[0]-'0';
		if(s[1] == 'm') return re;
		if(s[1] == 'p') return 9+re;
		if(s[1] == 's') return 18+re;
	}
	if(s[0] == 'E') return 28;
	if(s[0] == 'S') return 29;
	if(s[0] == 'W') return 30;
	if(s[0] == 'N') return 31;
	if(s[0] == 'Z') return 32;
	if(s[0] == 'B') return 33;
	if(s[0] == 'F') return 34;
}
typedef long long LL;
int cnt[50], bel[50];
LL ans, c[5][5], pw6[10];
bool bao[50];

int arr13[14] = { 0, 1, 9, 10, 18, 19, 27, 28, 29, 30, 31, 32, 33, 34 }; //指定的13张牌的编号
void solve13() {
	bool flg = 1;
	for(int i = 1; i <= 13; ++i) flg &= bool(cnt[arr13[i]]);
	if(flg) {
		LL now = 13;
		for(int i = 1; i <= 13; ++i)
			now = now * c[cnt[arr13[i]]][1] * (bao[arr13[i]] ? 2 : 1);
		for(int i = 1; i <= 13; ++i)
			ans = max(ans, now / c[cnt[arr13[i]]][1] * c[cnt[arr13[i]]][2] * (bao[arr13[i]] ? 2 : 1));
	}
}

void dfs(int i, int j, LL now) { //暴力枚举七对子
	if(j == 7) { ans = max(now, ans); return; }
	if(i > 34) return;
	LL tmp = now*pw6[7-j]*(1ll<<((7-j)<<1)); //剪枝,预估之后的最大值,如果小于等于当前答案就不用了搜下去了
	if(tmp <= ans) return;
	if(cnt[i] >= 2) {
		cnt[i] -= 2;
		dfs(i+1, j+1, now*c[cnt[i]+2][2]*(bao[i]?4:1));
		cnt[i] += 2;
	}
	if(tmp <= ans) return;
	dfs(i+1, j, now);
}
void solve7() { dfs(1, 0, 7); }

LL dp[36][2][5][5][5][5];
inline void upd(LL &x, LL y) { x = y > x ? y : x; }
inline int calc(int i, int j) { return i > 0 ? c[cnt[i]][j] * (bao[i] ? (1<<j) : 1) : 1; } //计算i选j张的贡献
void solve() {
	memset(dp, 0, sizeof dp);
	dp[1][0][0][0][0][0] = 1;
	for(int i = 1; i <= 34; ++i)
		for(int j = 0; j < 2; ++j)
			for(int k = 0; k <= 4; ++k)
				for(int a = 0; a <= 4; ++a)
					for(int b = 0; b <= 4; ++b)
						for(int c = 0; c <= 4; ++c) {
							LL val = dp[i][j][k][a][b][c];
							if(val) {
								upd(dp[i+1][j][k][b][c][0], val * calc(i-2, a)); 
								if(!j && cnt[i]-c >= 2)
									upd(dp[i][j+1][k][a][b][c+2], val);
								if(k<4 && cnt[i]-c >= 3)
									upd(dp[i][j][k+1][a][b][c+3], val);
								if(k<4 && bel[i] && i>2 && bel[i]==bel[i-2] && cnt[i]-c >= 1 && cnt[i-1]-b >= 1 && cnt[i-2]-a >= 1)
									upd(dp[i][j][k+1][a+1][b+1][c+1], val);
							}
					}
	for(int a = 0; a <= 4; ++a)
		for(int b = 0; b <= 4; ++b)
			for(int c = 0; c <= 4; ++c) //最后答案算上a,b,c
				upd(ans, dp[34][1][4][a][b][c] * calc(32, a) * calc(33, b) * calc(34, c)); 
}

int main () {
	//freopen("doraippai.in", "r", stdin);
//	freopen("doraippai.out", "w", stdout);
	for(int i = 1; i <= 9; ++i) bel[i] = 1;
	for(int i = 10; i <= 18; ++i) bel[i] = 2;
	for(int i = 19; i <= 27; ++i) bel[i] = 3;
	c[0][0] = 1;
	for(int i = 1; i <= 4; ++i) {
		c[i][0] = c[i][i] = 1;
		for(int j = 1; j < i; ++j)
			c[i][j] = c[i-1][j-1] + c[i-1][j];
	}
	pw6[0] = 1;
	for(int i = 1; i <= 7; ++i) pw6[i] = 6ll * pw6[i-1];
	int T; scanf("%d", &T); while(T--) {
		for(int i = 1; i <= 34; ++i)
			cnt[i] = 4, bao[i] = 0;
		char s[5];
		while(scanf("%s", s), s[0] != '0') {
			int num = getid(s);
			--cnt[num];
		}
		while(scanf("%s", s), s[0] != '0') {
			int num = getid(s);
			bao[num] = 1;
		}
		ans = 0;
		solve13();
		solve7();
		solve();
		printf("%lld\n", ans);
	}
}
posted @ 2019-12-16 21:16  _Ark  阅读(186)  评论(0编辑  收藏  举报