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