[bzoj4325][NOIP2015]斗地主

题目大意

现在给你n张牌,只能按照斗地主的规则出牌,规则如下:
王炸
一张单牌
一对牌
三张相同单牌
炸弹
顺子(不包括王和2,与斗地主相同需要连续至少5种)
双顺(即每种两张,至少连续3种,不包括王和2)
三顺(即每种三张,至少连续2种,不包括王和2)
三带一单
三带一对
四带两单
四带王炸
四带一对
四带两对
两个炸弹

DFS

我们可以使用dfs来做此题。

优化

首先,我们知道ans最多为14,我们加ans优化,即当前答案大于等于ans就退出。
我们发现,调转出牌次序没有影响,因此我们要避免同一种出牌方案被再次访问。
我们可以率先从耗牌多的开始,来辅助ans优化。
因此,我们可以按照三顺-双顺-顺-四带-三带的次序来做,这些都做完后,可知有多少种就还需多少次出牌。
王炸应在一开始特判,大王小王应当不同码数的牌来算,避免出现三带王炸和四带王炸带对。
做顺时,可以找出每一段最长l~r使得这个区间能顺,然后根据长度做到底怎么打出去这个顺。
我们发现,做带时牌的码数已经无用,我们可以用p[i]表示还剩i张的有多少种进行优化。
这样就能过,注意细节,很容易打挂!

参考程序

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
int a[20],p[7];
int i,j,k,l,t,n,m,ans,ca,gjx,wzd;
void solve2(int y){
    if (y+15-p[0]<ans) ans=y+15-p[0];
    if (y>=ans) return;
    int i,j;
    if (p[4]>=1){
        fo(i,2,4)
            fo(j,2,4){
                if ((i==j&&p[i]>=2)||(i!=j&&p[i]&&p[j])){
                    if (i==4&&j!=i&&p[4]<2) continue;
                    else if (i==4&&j==i&&p[4]<3) continue;
                    else if (i!=4&&j==4&&p[4]<2) continue;
                    p[4]--;p[0]++;p[i]--;p[i-2]++;p[j]--;p[j-2]++;
                    solve2(y+1);
                    p[4]++;p[0]--;p[i]++;p[i-2]--;p[j]++;p[j-2]--;
                }
            }
        fo(i,1,4)
            fo(j,1,4){
                if ((i==j&&p[i]>=2)||(i!=j&&p[i]&&p[j])){
                    if (i==4&&j!=i&&p[4]<2) continue;
                    else if (i==4&&j==i&&p[4]<3) continue;
                    else if (i!=4&&j==4&&p[4]<2) continue;
                    p[4]--;p[0]++;p[i]--;p[i-1]++;p[j]--;p[j-1]++;
                    solve2(y+1);
                    p[4]++;p[0]--;p[i]++;p[i-1]--;p[j]++;p[j-1]--;
                }
            }
        fo(i,2,4){
            if (i==4&&p[4]<2) continue;
            if (p[i]<1) continue;
            p[4]--;p[0]++;p[i]--;p[i-2]++;
            solve2(y+1);
            p[4]++;p[0]--;p[i]++;p[i-2]--;
        }
    }
    if (p[3]>=1){
        fo(i,2,4){
            if (i==3&&p[3]<2) continue;
            if (p[i]<1) continue;
            p[3]--;p[0]++;p[i]--;p[i-2]++;
            solve2(y+1);
            p[3]++;p[0]--;p[i]++;p[i-2]--;
        }
        fo(i,1,4){
            if (i==3&&p[3]<2) continue;
            if (p[i]<1) continue;
            p[3]--;p[0]++;p[i]--;p[i-1]++;
            solve2(y+1);
            p[3]++;p[0]--;p[i]++;p[i-1]--;
        }
    }
    if (p[4]>=1){
        fo(i,2,4){
            if (i==4&&p[4]<2) continue;
            if (p[i]<1) continue;
            p[4]--;p[1]++;p[i]--;p[i-2]++;
            solve2(y+1);
            p[4]++;p[1]--;p[i]++;p[i-2]--;
        }
        fo(i,1,4){
            if (i==4&&p[4]<2) continue;
            if (p[i]<1) continue;
            p[4]--;p[1]++;p[i]--;p[i-1]++;
            solve2(y+1);
            p[4]++;p[1]--;p[i]++;p[i-1]--;
        }
    }
    if (p[4]>=2){
        p[4]-=2;p[0]+=2;
        solve2(y+1);
        p[4]+=2;p[0]-=2;
    }

}
void solve1(int y){
    if (y>=ans) return;
    int i,j,l=1,r;
    fill(p,p+5,0);
    fo(i,1,15) p[a[i]]++;
    if (p[0]==15){
        ans=y;
        return;
    }
    solve2(y);
        fo(i,1,13){
            if (a[i]<3||i==13){
                while (1){
                r=i-1;
                if (l>r||l==r){
                    l=i+1;
                    break;
                }
                fo(j,l,r) a[j]-=3;
                fd(j,r,l+1){
                    solve1(y+1);
                    a[j]+=3;
                }
                a[l]+=3;
                l++;
                }
            }
        }
    l=1;
        fo(i,1,13){
            if (a[i]<2||i==13){
                while (1){
                r=i-1;
                if (l>r||r-l<2){
                    l=i+1;
                    break;
                }
                fo(j,l,r) a[j]-=2;
                fd(j,r,l+2){
                    solve1(y+1);
                    a[j]+=2;
                }
                a[l]+=2;a[l+1]+=2;
                l++;
                }
            }
        }
    l=1;
        fo(i,1,13){
            if (a[i]<1||i==13){
                while (1){
                r=i-1;
                if (l>r||r-l<4){
                    l=i+1;
                    break;
                }
                fo(j,l,r) a[j]--;
                fd(j,r,l+4){
                    solve1(y+1);
                    a[j]++;
                }
                a[l]++;a[l+1]++;a[l+2]++;a[l+3]++;
                l++;
                }
            }
        }
}
int main(){
    freopen("landlords.in","r",stdin);freopen("landlords.out","w",stdout);
    scanf("%d%d",&ca,&n);
    while (ca--){
        fill(a+1,a+16,0);
        fo(i,1,n){
            scanf("%d%d",&gjx,&wzd);
            if (gjx>=3&&gjx<=13) a[gjx-2]++;
            else if (gjx==2) a[13]++;
            else if (gjx==1) a[12]++;
            else if (wzd==1) a[14]++;
            else a[15]++;
        }
        ans=14;
        if (a[14]&&a[15]){
            a[14]--;a[15]--;
            solve1(1);
            a[14]++;a[15]++;
        }
        solve1(0);
        printf("%d\n",ans);
    }
    fclose(stdin);fclose(stdout);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值