UOJ 147|NOIP 2015|斗地主|搜索|贪心

你有一些扑克牌,有一些出牌方式,问最少出多少张牌才能全部打光。
分别有:王炸、炸弹、单张、对子、三张、三带一、三带二、四带两张、四带两对、单顺子、双顺子、三顺子。
顺子不包含大小王和2。

题目:http://uoj.ac/problem/147

没有飞机真是可惜,欢乐斗地主的飞机很爽的说
UOJ中途加强数据QwQ。

嘛,考虑搜索。
(还记得考场上的程序写的很丑)

显然对于一个出牌的方案,顺序是无关的,因此考虑优先出顺子,顺子出的牌最多是原因之一。
dfs搜索出顺子的方案。
那么对于每种出顺子的方案,我们可以知道还剩哪些牌,本着多出牌的原则,考虑四张和三张的牌带其他1张2张的牌出掉即可:依次出掉四带两对、四带两张、三带二、三带一,最后再出单种牌(1~4张)。
当然按照这个顺序出牌就不会出现某种牌有4张但一次只出掉3张的情况,带的对子和单牌也是,即每种牌只会一次全部出完。
然后贪心无法处理(感觉是这样?)顺子,所以必须搜顺子。

然后就没啥了。
最优性剪枝是照例有的。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define FOR(i,j,k) for(int i=j;i<=k;i++)
#define FORD(i,j,k) for(int i=j;i>=k;i--)
const int card[] = {0, 4, 2, 1};
int a[16], b[16], ans;
int sum() {
#define p(i,x,j,y) while (b[i] >= x && b[j] >= y) b[i] -= x, b[j] -= y, ++s
    int s = 0;
    memset(b, 0, sizeof b);
    FOR(i,0,14) if (i != 1) ++b[a[i]];
    p(4, 1, 2, 2); p(4, 1, 1, 2); p(3, 1, 2, 1); p(3, 1, 1, 1);
    return s + b[4] + b[3] + b[2] + b[1];
}
void dfs(int dep) {
    if (dep >= ans) return;
    int j;
    FORD(x,3,1) FOR(i,3,13) {
        for (j = i; j <= 14 && a[j] >= x; j++);
        int len = (--j) - i + 1;
        if (len > card[x]) {
            FOR(k,i,i+card[x]-1) a[k] -= x;
            FOR(k,i+card[x],j) a[k] -= x, dfs(dep + 1);
            FOR(k,i,j) a[k] += x;
        }
    }
    ans = min(ans, dep + sum());
}

int main() {
    int t, n, x, y;
    scanf("%d%d", &t, &n);
    while (t--) {
        memset(a, 0, sizeof a);
        FOR(i,1,n) {
            scanf("%d%d", &x, &y);
            if (x == 1) x = 14;
            ++a[x];
        }
        ans = min(n, 13);
        dfs(0);
        printf("%d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值