bzoj1028 [JSOI2007]麻将
1028: [JSOI2007]麻将
Time Limit: 1 Sec Memory Limit: 162 MBSubmit: 1337 Solved: 601
[Submit][Status][Discuss]
Description
麻将是中国传统的娱乐工具之一。麻将牌的牌可以分为字牌(共有东、南、西、北、中、发、白七种)和序数牌(分为条子、饼子、万子三种花色,每种花色各有一到九的九种牌),每种牌各四张。在麻将中,通常情况下一组和了的牌(即完成的牌)由十四张牌组成。十四张牌中的两张组成对子(即完全相同的两张牌),剩余的十二张组成三张一组的四组,每一组须为顺子(即同花色且序数相连的序数牌,例如条子的三、四、五)或者是刻子(即完全相同的三张牌)。一组听牌的牌是指一组十三张牌,且再加上某一张牌就可以组成和牌。那一张加上的牌可以称为等待牌。 在这里,我们考虑一种特殊的麻将。在这种特殊的麻将里,没有字牌,花色也只有一种。但是,序数不被限制在一到九的范围内,而是在1到n的范围内。同时,也没有每一种牌四张的限制。一组和了的牌由3m + 2张牌组成,其中两张组成对子,其余3m张组成三张一组的m组,每组须为顺子或刻子。现给出一组3m + 1张的牌,要求判断该组牌是否为听牌(即还差一张就可以和牌)。如果是的话,输出所有可能的等待牌。
Input
包含两行。第一行包含两个由空格隔开整数n, m (9<=n<=400, 4<=m<=1000)。第二行包含3m + 1个由空格隔开整数,每个数均在范围1到n之内。这些数代表要求判断听牌的牌的序数。
Output
输出为一行。如果该组牌为听牌,则输出所有的可能的等待牌的序数,数字之间用一个空格隔开。所有的序数必须按从小到大的顺序输出。如果该组牌不是听牌,则输出"NO"。
Sample Input
1 1 2 2 3 3 5 5 5 7 8 8 8
Sample Output
HINT
Source
题意:很好懂,前面麻将的介绍基本不用看
精简后的题意
刻子(即完全相同的三张牌 顺子(序数相连的牌,例如三、四、五) 对子(即完全相同的两张牌
序数在1到n的范围内。每一种牌张数无限制。
一组和牌由3m + 2张牌组成,其中两张组成对子,其余3m张组成三张一组的m组,每组须为顺子或刻子。
现给出一组3m + 1张的牌,要求判断该组牌是否为听牌(即还差一张就可以和牌)。如果是的话,输出所有可能的等待牌。
分析:首先看到 n<=400
那么我们会想到的是枚举要加进哪张牌(毕竟要输出的是每一种方案而不是方案数)
那加进之后如何检验。。。。。
我们这样想问题 -> 数字为i的牌只能与 i+1, i+2组成顺子,而不考虑与i-1,i-2组成顺子(即规定一个方向,以免重复和为了下面叙述方便)
那么数字为n-1,n的牌一定要是若干个刻子(在抽走了组成了顺子的牌之后)
那么显然至少有(a[n]%3)个顺子
若顺子的个数大于等于3,即3*k+x个i,i+1,i+2这样个顺子,那么可以当成k个i的刻子和k个i+1的刻子和k个i+2的刻子以及x个顺子(x<3)
所以可以证明倒着枚举数字,优先安排刻子的,其次安排顺子的贪心顺序是正确的
当然正着枚举也是一样的道理
听说这题有dp做法,我得好好想想,不过在网上找不到
这是倒着枚举的代码,其实正着枚举要简单写(其实两代码一样)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <deque> 6 #include <vector> 7 #include <queue> 8 #include <iostream> 9 #include <algorithm> 10 #include <map> 11 #include <set> 12 #include <ctime> 13 using namespace std; 14 typedef long long LL; 15 typedef double DB; 16 #define For(i, s, t) for(int i = (s); i <= (t); i++) 17 #define Ford(i, s, t) for(int i = (s); i >= (t); i--) 18 #define Rep(i, t) for(int i = (0); i < (t); i++) 19 #define Repn(i, t) for(int i = ((t)-1); i >= (0); i--) 20 #define rep(i, x, t) for(int i = (x); i < (t); i++) 21 #define MIT (2147483647) 22 #define INF (1000000001) 23 #define MLL (1000000000000000001LL) 24 #define sz(x) ((int) (x).size()) 25 #define clr(x, y) memset(x, y, sizeof(x)) 26 #define puf push_front 27 #define pub push_back 28 #define pof pop_front 29 #define pob pop_back 30 #define ft first 31 #define sd second 32 #define mk make_pair 33 inline void SetIO(string Name) { 34 string Input = Name+".in", 35 Output = Name+".out"; 36 freopen(Input.c_str(), "r", stdin), 37 freopen(Output.c_str(), "w", stdout); 38 } 39 40 const int N = 410; 41 int n, m, Arr[N]; 42 int Ans[N], Tot; 43 44 inline void Input() { 45 scanf("%d%d", &n, &m); 46 For(i, 1, 3*m+1) { 47 int x; 48 scanf("%d", &x); 49 Arr[x]++; 50 } 51 } 52 53 int Tmp[N]; 54 inline bool Check(int x) { 55 For(i, 1, n) { 56 For(j, 1, n) Tmp[j] = Arr[j]; 57 Tmp[x]++; 58 Tmp[i] -= 2; 59 if(Tmp[i] < 0) continue; 60 61 bool flag = 1; 62 Ford(j, n, 3) { 63 if(Tmp[j] < 0) { 64 flag = 0; 65 break; 66 } 67 if(!Tmp[j]) continue; 68 Tmp[j] %= 3; 69 Tmp[j-1] -= Tmp[j]; 70 Tmp[j-2] -= Tmp[j]; 71 Tmp[j] = 0; 72 } 73 Tmp[1] %= 3, Tmp[2] %= 3; 74 if(Tmp[1] || Tmp[2]) flag = 0; 75 76 if(flag) return 1; 77 } 78 return 0; 79 } 80 81 inline void Solve() { 82 For(i, 1, n) 83 if(Check(i)) Ans[++Tot] = i; 84 85 if(!Tot) puts("NO"); 86 else { 87 For(i, 1, Tot-1) printf("%d ", Ans[i]); 88 printf("%d\n", Ans[Tot]); 89 } 90 } 91 92 int main() { 93 #ifndef ONLINE_JUDGE 94 SetIO("1028"); 95 #endif 96 Input(); 97 Solve(); 98 return 0; 99 }
这是正着枚举
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <deque> 6 #include <vector> 7 #include <queue> 8 #include <iostream> 9 #include <algorithm> 10 #include <map> 11 #include <set> 12 #include <ctime> 13 using namespace std; 14 typedef long long LL; 15 typedef double DB; 16 #define For(i, s, t) for(int i = (s); i <= (t); i++) 17 #define Ford(i, s, t) for(int i = (s); i >= (t); i--) 18 #define Rep(i, t) for(int i = (0); i < (t); i++) 19 #define Repn(i, t) for(int i = ((t)-1); i >= (0); i--) 20 #define rep(i, x, t) for(int i = (x); i < (t); i++) 21 #define MIT (2147483647) 22 #define INF (1000000001) 23 #define MLL (1000000000000000001LL) 24 #define sz(x) ((int) (x).size()) 25 #define clr(x, y) memset(x, y, sizeof(x)) 26 #define puf push_front 27 #define pub push_back 28 #define pof pop_front 29 #define pob pop_back 30 #define ft first 31 #define sd second 32 #define mk make_pair 33 inline void SetIO(string Name) { 34 string Input = Name+".in", 35 Output = Name+".out"; 36 freopen(Input.c_str(), "r", stdin), 37 freopen(Output.c_str(), "w", stdout); 38 } 39 40 const int N = 410; 41 int n, m, Arr[N]; 42 int Ans[N], Tot; 43 44 inline void Input() { 45 scanf("%d%d", &n, &m); 46 For(i, 1, 3*m+1) { 47 int x; 48 scanf("%d", &x); 49 Arr[x]++; 50 } 51 } 52 53 int Tmp[N]; 54 inline bool Check(int x) { 55 For(i, 1, n) { 56 For(j, 1, n+2) Tmp[j] = Arr[j]; 57 Tmp[x]++; 58 Tmp[i] -= 2; 59 if(Tmp[i] < 0) continue; 60 61 bool flag = 1; 62 For(j, 1, n+2) { 63 if(Tmp[j] < 0) { 64 flag = 0; 65 break; 66 } 67 if(!Tmp[j]) continue; 68 Tmp[j] %= 3; 69 Tmp[j+1] -= Tmp[j]; 70 Tmp[j+2] -= Tmp[j]; 71 Tmp[j] = 0; 72 } 73 if(flag) return 1; 74 } 75 return 0; 76 } 77 78 inline void Solve() { 79 For(i, 1, n) 80 if(Check(i)) Ans[++Tot] = i; 81 82 if(!Tot) puts("NO"); 83 else { 84 For(i, 1, Tot-1) printf("%d ", Ans[i]); 85 printf("%d\n", Ans[Tot]); 86 } 87 } 88 89 int main() { 90 #ifndef ONLINE_JUDGE 91 SetIO("1028"); 92 #endif 93 Input(); 94 Solve(); 95 return 0; 96 }
//-------------------------------------------------------
大概是想出来如何dp了,不过比较麻烦,但复杂度较低
先是枚举加入的数字,然后dp求解
dp[2][400][1000][1000][1000]
第一位表示是否选择了对子,第二位表示当前进行到了第个数字(设为第x个数字),第三位-第五位表示从第x,x+1,x+2个数字的个数
显然有效状态很少,我们可以用队列和Hash实现这个dp过程
转移时对于每一个状态的转移参考那个优先刻子,其次顺子的贪心策略,推到下一个状态
整个过程的复杂度应该是O(n^2)
听说还有O(n)的dp做法,我再想想