P4050 [JSOI2007] 麻将 题解

模拟不要怎么搞都暴搜!

P4050 [JSOI2007] 麻将

题面:

题目描述

麻将是中国传统的娱乐工具之一。麻将牌的牌可以分为字牌(共有东、南、西、北、中、发、白七种)和序数牌(分为条子、饼子、万子三种花色,每种花色各有一到九的九种牌),每种牌各四张。

在麻将中,通常情况下一组和了的牌(即完成的牌)由十四张牌组成。十四张牌中的两张组成对子(即完全相同的两张牌),剩余的十二张组成三张一组的四组,每一组须为顺子(即同花色且序数相连的序数牌,例如条子的三、四、五)或者是刻子(即完全相同的三张牌)。一组听牌的牌是指一组十三张牌,且再加上某一张牌就可以组成和牌。那一张加上的牌可以称为等待牌。

在这里,我们考虑一种特殊的麻将。在这种特殊的麻将里,没有字牌,花色也只有一种。但是,序数不被限制在一到九的范围内,而是在1到n的范围内。同时,也没有每一种牌四张的限制。一组和了的牌由3m + 2张牌组成,其中两张组成对子,其余3m张组成三张一组的m组,每组须为顺子或刻子。现给出一组3m + 1张的牌,要求判断该组牌是否为听牌(即还差一张就可以和牌)。如果是的话,输出所有可能的等待牌。

输入格式

包含两行。第一行包含两个由空格隔开整数n, m (9<=n<=400, 4<=m<=1000)。第二行包含3m + 1个由空格隔开整数,每个数均在范围1到n之内。这些数代表要求判断听牌的牌的序数。

输出格式

输出为一行。如果该组牌为听牌,则输出所有的可能的等待牌的序数,数字之间用一个空格隔开。所有的序数必须按从小到大的顺序输出。如果该组牌不是听牌,则输出"NO"。

样例 #1
样例输入 #1
9 4
1 1 2 2 3 3 5 5 5 7 8 8 8
样例输出 #1
6 7 9

暴搜只能喜提 10 ∼ 20 10 \sim 20 1020

缺字,我们补上,判断是否合法。(摈弃一些正向思维)

我们再来看牌型的影响,对子、刻子 影响的是本身,而顺子影响的是连续的 3 3 3 个数.

由于牌中只能有一个对子,我们考虑扣掉,枚举对子,判断是否合法

当我们判断到 x x x 时,如果 x x x 可以凑成刻子,毫不犹豫凑成,因为他不影响到 x + 1 , x + 2 x + 1,x + 2 x+1,x+2 这些位置,

我们枚举顺子的起点为 i i i

因为 如果到 i + 1 i + 1 i+1 后, i i i 位置还没有清完,说明失败了!

所以我们会不惜一切代价将凑完刻子的剩余的 i i i 牌用来凑顺子,如果凑不出来,就说明失败了

注意这些就可以在 O ( n 3 ) \mathcal{O}(n^3) O(n3) 下跑过

AC-code:

#include<bits/stdc++.h>
using namespace std;
		
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}

int n,m,card[405];
namespace workplace{
int a[405];
bool check() {
	for(int i = 1;i<=n;i++) {
		if(card[i] >= 2) {
			card[i] -= 2;
			bool ok = true;
			for(int j = 1;j<=n + 2;j++) a[j] = card[j];
			for(int j = 1;j<=n + 2;j++) {
				if(a[j] < 0) {
					ok = false;
					break;
				}
				a[j] %= 3;
				a[j + 1] -= a[j];
				a[j + 2] -= a[j];
			}
			card[i] += 2;
			if(ok) return true;
		}
	}
	return false;
}

void solve() {
	bool non = 0,re = false;
	for(int i = 1;i<=n;i++) {
		card[i]++;
		non |= (re = check());
		if(re)
			wt(i),putchar(' ');
		card[i]--;
	}
	if(!non) puts("NO");
}
}

signed main() {
	n = rd(),m = rd();
	for(int i = 1;i<=3 * m + 1;i++) card[rd()]++;
	workplace::solve();
	return 0;
}
根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值