[NOIp 2015]斗地主

Description

 牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系根据牌的数码表示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由n张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。具体规则如下:

Input

第一行包含用空格隔开的2个正整数T,N,表示手牌的组数以及每组手牌的张数。

接下来T组数据,每组数据N行,每行一个非负整数对Ai,Bi,表示一张牌,其中Ai表示牌的数码,Bi表示牌的花色,中间用空格隔开。特别的,我们用1来表示数码A,11表示数码J,12表示数码Q,13表示数码K;黑桃、红心、梅花、方片分别用1-4来表示;小王的表示方法为01,大王的表示方法为02。

Output

共T行,每行一个整数,表示打光第T组手牌的最少次数。

Sample Input

1 8
7 4
8 4
9 1
10 4
11 1
5 1
1 4
1 1

Sample Output

3

HINT

共有1组手牌,包含8张牌:方片7,方片8,黑桃9,方片10,黑桃J,黑桃5,方

片A以及黑桃A。可以通过打单顺子(方片7,方片8,黑桃9,方片10,黑桃J),单张
牌(黑桃5)以及对子牌(黑桃A以及方片A)在3次内打光。
T<=10
N<=23

题解

抓取有用信息:

出牌顺序不影响出牌次数。

30分算法:

1、$T≤100$,$n≤4$;
2、先特判掉三带一的情况,然后有几种不同点数的牌,答案就是几;注意两张王可以看成是相同点数;
3、时间复杂度$O(T*n)$

100分算法:

1、$T≤10$,$n≤23$;
2、既然出牌顺序不影响,那么不妨先出对子,包括单顺、双顺、三顺。具体就是直接暴力枚举每一个顺子,然后出掉,再枚举顺子,再出掉......
3、这样可以过吗?
一个顺子至少有$5$张牌,最多出$4$组顺子,递归层数很小;
然后在一组牌内可以产生$O(K^2)$个顺子,其中$K$表示能成为顺子组成部分的牌的种数,在这里$K=12$,然后这里的复杂度就是$O(K^8)$,看起来很大,其实实测完全可以跑出来;
4、然后就可以不考虑顺子了,那么对于剩下的牌,我们就只能一个一个或者一对一对或者一带一带地出,也就是说出牌次数与牌的点数无关了;
5、那么我们可以预处理一个$dp[a][b][c][d]$,表示手牌有"$d$张单牌,$c$个对子,$b$个三张,$a$个炸弹"的时候,把牌出完的最少次数。
6、可以动态规划求解,$joker$可以拿出单独讨论。
7、时间复杂度$O(n^4+T*K^8)$。

这道题还有数据增强版,就是多考虑几个条件,把牌拆开(详见代码中的$extra$)。

 1 #include <set>
 2 #include <map>
 3 #include <ctime>
 4 #include <cmath>
 5 #include <queue>
 6 #include <stack>
 7 #include <vector>
 8 #include <cstdio>
 9 #include <string>
10 #include <cstring>
11 #include <cstdlib>
12 #include <iostream>
13 #include <algorithm>
14 #define LL long long
15 #define Max(a, b) ((a) > (b) ? (a) : (b))
16 #define Min(a, b) ((a) < (b) ? (a) : (b))
17 using namespace std;
18 const int INF = ~0u>>1;
19 const int lenth[4] = {0, 5, 3, 2};
20 
21 int n, t;
22 int card[20], f[25][25][25][25];
23 int ans;
24 
25 int getrest(int r1, int r2, int r3, int r4, int joker){
26     if (joker == 1) r1++,joker--;
27     if (joker) return Min(f[r4][r3][r2][r1+2], f[r4][r3][r2][r1]+1);
28     return f[r4][r3][r2][r1];
29 }
30 void dfs(int t){
31     if (t >= ans) return;
32     int c[5] = {0};
33     for (int i = 2; i <= 14; i++) c[card[i]]++;
34     ans = Min(ans, t+getrest(c[1], c[2], c[3], c[4], card[0]));
35     for (int len = 1; len <= 3; len++)
36       for (int i = 3; i <= 14; i++){
37           int j = i;
38           for (;j <= 14 && card[j] >= len; j++){
39             card[j] -= len;
40             if (j-i+1 >= lenth[len]) dfs(t+1);
41           }
42           for (j--; j >= i; j--) card[j] += len;
43       }
44 }
45 void pre(){
46     memset(f, 127/3, sizeof(f));
47     f[0][0][0][0] = 0;
48     for (int i = 0; i <= n; i++)
49       for (int j = 0; j <= n; j++)
50           for (int p = 0; p <= n; p++)
51             for (int q = 0; q <= n; q++)
52                 if (i*4+j*3+p*2+q <= n)
53                 {
54                   f[i][j][p][q] = i+j+p+q;
55                   if (i){
56                       if (p >= 2) f[i][j][p][q] = Min(f[i][j][p][q], f[i-1][j][p-2][q]+1);//四带两对
57                       if (q >= 2) f[i][j][p][q] = Min(f[i][j][p][q], f[i-1][j][p][q-2]+1);//四带二
58                       if (p) f[i][j][p][q] = Min(f[i][j][p][q], f[i][j][p-1][q+2]);//extra:把对子拆成一个单的
59                       f[i][j][p][q] = Min(f[i][j][p][q], f[i-1][j][p+2][q]);//extra:把炸拆成两对
60                       f[i][j][p][q] = Min(f[i][j][p][q], f[i-1][j+1][p][q+1]);//extra:把炸拆成单张和三张
61                       f[i][j][p][q] = Min(f[i][j][p][q], f[i-1][j][p][q]+1);//出炸
62                   }
63                   if (j){
64                       if (p) f[i][j][p][q] = Min(f[i][j][p][q], f[i][j-1][p-1][q]+1);//三带一对
65                       if (q) f[i][j][p][q] = Min(f[i][j][p][q], f[i][j-1][p][q-1]+1);//三带一
66                       f[i][j][p][q] = Min(f[i][j][p][q], f[i][j-1][p+1][q+1]);//extra:三拆成二+一
67                       f[i][j][p][q] = Min(f[i][j][p][q], f[i][j-1][p][q]+1);//直接出三张
68                   }
69                   if (p) f[i][j][p][q] = Min(f[i][j][p][q], f[i][j][p-1][q]+1);//直接出对子
70                   if (q) f[i][j][p][q] = Min(f[i][j][p][q], f[i][j][p][q-1]+1);//直接出单张
71                 }
72 }
73 
74 int main(){
75     scanf("%d%d", &t, &n);
76     pre();
77     while (t--){
78       memset(card, 0, sizeof(card));
79       int a, b;
80       ans = n;
81       for (int i = 1; i <= n; i++){
82           scanf("%d%d", &a, &b);
83           if (a == 1) card[14]++;
84           else card[a]++;
85       }
86       dfs(0);
87       printf("%d\n", ans);
88     }
89     return 0;
90 }

 

posted @ 2017-09-03 21:05  NaVi_Awson  阅读(432)  评论(0编辑  收藏  举报